线程状态
- 新建状态(New): 新创建一个线程对象。
- 就绪状态(Runnable): 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行状态(Running): 就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(1)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(3)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态结束、join()状态中断、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep不会释放持有的锁) - 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程调度
Java的线程调度是抢占式调度,线程具有线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),共十个级别(1-10),当两个线程均处于就绪状态时,优先级越高的线程越容易被系统选择执行。但是Java线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统。
常用函数
Thread.sleep(long millis):
使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,转为就绪(Runnable)状态。sleep()
平台移植性好。sleep()
不释放锁。Object.wait():
使当前的线程等待,直到其他线程调用此对象的notify()
方法或notifyAll()
唤醒方法。这两个唤醒方法也是Object类中的方法,行为等价于调用wait(0)
一样。Thread.yield():
暂停当前正在执行的线程对象,使当前线程转为就绪状态。Thread.join():
等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。Object.notify()/Object.notifyAll():
唤醒在此对象监视器上等待的单个/全部线程。wait()/notify()/notifyAll()
都应该在synchronized
修饰的块中执行。
多线程实现方式
继承Thread,实现run函数
1
2
3
4
5
6
7
8
9
10
11class Thread1 extends Thread{
public void run() {
//执行操作
}
}
public class Main {
public static void main(String[] args) {
Thread1 mTh1=new Thread1();
mTh1.start();
}
}实现Runnable接口,实现run函数
1
2
3
4
5
6
7
8
9
10class Thread1 implements Runnable{
public void run() {
//执行操作
}
}
public class Main {
public static void main(String[] args) {
new Thread(new Thread1()).start();
}
}Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
线程同步
- synchronized
当修饰普通方法时,同一个对象调用该方法需要获得同步锁,否则阻塞;当修饰静态方法时,会锁住整个类;可修饰一个语句块。
- volatile
volatile修饰的变量对所有线程可见,当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量强制刷新到主内存中去,这个写操作会导致其他线程中的缓存无效。但是volatile不会提供原子操作,对于复合操作如num++不能实现线程同步。
- ReentrantLock
1
2
3Lock lock = new ReentrantLock();
lock.lock();
lock.unlock();
使用lock()
加锁,使用unlock()
释放锁。
ThreadLocal
- ThreadLocal 并不解决线程间共享数据的问题
- ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
- 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题
- ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景
concurrent
使用java.util.concurrent包提供的工具,如ConcurrentHashMap。
- concurrent.atomic
使用java.util.concurrent.atomic包提供的原子变量实现线程同步,如AtomicInteger。
线程池(ThreadPoolExecutor)
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间,而使用线程池就可以对线程进行复用。线程池详解
四种线程池
- 定长线程池(FixedThreadPool)
- 特点:只有核心线程 & 不会被回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待)
- 应用场景:控制线程最大并发数
- 定时线程池(ScheduledThreadPool)
- 特点:核心线程数量固定、非核心线程数量无限制(闲置时马上回收)
- 应用场景:执行定时 / 周期性 任务
- 可缓存线程池(CachedThreadPool)
- 特点:只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时)、任何线程任务到来都会立刻执行,不需要等待
- 应用场景:执行大量、耗时少的线程任务
- 单线程化线程池(SingleThreadExecutor)
- 特点:只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)
- 应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等