线程的生命周期
首先,让我们根据一张图来了解一下线程的生命周期.
线程的生命周期包括五个状态:
-
新建状态
New
新建状态是在线程实例在被创建之后但是还没调用
start()
方法之前所处的状态,不做深入研究. -
可运行状态
Runnable
此时线程已经调用了
start()
,但是因为没有被CPU调度,即没有获得CPU运行时间片,所以只是处于可运行状态,并未真的运行. -
运行状态
Running
线程获得了CPU时间片,开始执行线程体的内容.
-
阻塞状态
Blocked
线程的阻塞状态是线程最复杂的一个状态,因为一个线程可能因为多种原因被阻塞.在这里我们先从线程运行的条件讲起.
若线程中没有同步代码块,则线程无需关心对象锁的获取,此时线程可能被阻塞的原因有:
- 线程被操作系统调度出CPU,比如线程进行IO耗时操作
- 调用
Thread.sleep()
,该方法会导致线程休眠,从而释放CPU资源 - 线程调用了其他线程的
join
方法,此方法会等待join方法的调用线程完全执行结束后才继续执行当前线程,从而导致当前线程阻塞 - 当前线程调用
yield
方法,导致线程释放CPU而进入可执行状态.
若线程中有同步代码块,则线程要运行必须同时满足获得同步代码块所需对象锁以及CPU资源两个条件,此时被阻塞的原因除了上面原因以外,还包括:
- 线程尝试获取对象锁失败从而阻塞
- 线程在执行过长中调用了同步对象的
wait()
方法,主动释放CPU资源以及对象锁,从而阻塞.
结合以上两种情况,一个线程被阻塞的情况由大概有三种:
-
等待阻塞
在当前线程已经获得对象锁以及CPU的前提下,调用同步对象的
wait()
方法,从而导致当前线程释放CPU,释放对象锁,将当前线程放入同步对象的waitSe
t集合中.waitSet
中的阻塞线程只有通过同步对象调用notify
或notifyAll
方法才能再次被唤醒,唤醒后进入可执行状态. -
同步阻塞
当前线程在运行到同步代码块时,尝试获取同步对象的对象所失败而被放入该对象的
entrySet
,即我们说的锁池集合中.entrySet
中的线程等待获取同步对象的锁然后进入可运行状态.一切释放锁的操作都可能唤醒里面的线程,如:- 持有同步对象锁的线程退出同步代码块(执行结束等)
- 同步对象调用
wait()
,该方法会释放CPU和锁
-
其他阻塞
当前线程调用
Thread.sleep(long millis)
方法,导致当前线程休眠,此操作会释放CPU,但是已经持有的对象锁不会释放.同样的还有在线程a中调用线程
b.join()
方法,该方法导致a会等待b执行完毕之后才会继续执行,a会让出CPU资源,但不确定join是否释放锁.join的底层实现仍是
wait
,详细请参考Join()会不会释放锁?
线程因为满足运行条件而从这三个阻塞状态中解脱出来时都会被放到Runnable队列中等待系统调度.
-
死亡状态
Dead
线程执行结束后的状态.值得一说的是,线程也是一个对象,也有对象锁,在线程退出的时候会主动调用自己的notify函数来调用在自己
waitSet
里面的线程.
wait
会释放锁,释放的前提是已经获得了锁,所以必须要在sychronized同步代码块中调用wait,不然就相当于在不确定是否获得锁的情况下去释放锁,这也是有问题的.
sychronized-wait-notify
是配套使用的,设计目的是为了完成线程间通信.