众所周知,
Java并发系列编程
一直都是Java程序员难以轻易绕过的山,可谓之小高之山也。Java生态圈中提供了很是丰富的并发编程类库,可是这样子也造就了很是多的人知其然而不知其因此然,不少人只会用,殊不知其底层的运行机制,不知其优点与缺陷,也就没法将其融会贯通,作到信手拈来。况且,即使很是完善的类库也没法知足全部的业务需求,适当的时候咱们可能要本身编写类库来支撑本身的业务。在这个系列中,咱们一块儿来深刻学习并发编程,一块儿成长。本人能力有限,如有错误,请及时指正。程序员
想要学习好多线程编程,无疑要先明白,线程在整个运行期间的状态,又是经过何种机制,或者说何种方式让线程在这些状态之间切换?面试
如上图,大体能够说明Java线程的生命周期。编程
在多线程编程中,咱们一般会使用 Thread
, Runnable
关键字来建立线程,以下:多线程
Thread thread = new Thread(new Runnable(){
并发
@Override
ide
public void run() {
学习
System.out.println("thread");
spa
}
操作系统
});
.net
当这个线程被建立出来以后,若是你没有调用 start()
(或者其余方式好比当如线程池中调用 submit()
),它与普通对象并没有区别(有面试官可能会在这里考你,至少我经历过)。此时OS调度器不会为它分配CPU执行须要的时间分片。
当线程被建立出来以后,它必须进入就绪状态,才可能被分配CPU时间分片,才能够可能被操做系统调度。一般状况下,咱们会经过调用 start()
,或者线程池的 submit()
让线程进入就绪状态,以下:
thread.start(); // 普通线程
ExecutorService executor = Executors.newSingleThreadExecutor();// 建立线程池并将线程提交给线程池管理
executor.submit(thread);
线程进入就绪状态大概有以下几种方式:
线程重新建状态进入就绪状态,好比(start(),executor.submit())
线程从运行状态恢复到就绪状态,(好比正在执行的线程被挂起,CPU时间分片用完,yield())
线程从阻塞状态恢复到就绪状态(好比sleep执行完毕,join执行完毕)
在锁池中等待的锁拿到了锁的权限
当线程处于就绪状态时,随时有可能被操做系统调度。线程从就绪状态到被操做系统调度称为线程进入运行状态,语义上是: run()
方法正在接受操做系统调度,正在被执行。
当线程从运行状态调用了 sleep()
或者 join()
等方法的时候,会进入阻塞状态,等待执行时间到,或者调用 join()
的线程执行完毕,线程会自动进入就绪状态,以下:
try {
thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
当线程执行 run()
方法完毕,或者由于发生异常而被中断,或者 interrupt()
(另,在jdk中依旧能够看到一些已经被废弃的诸如 stop(),supend()
等等),线程会进入死亡状态。若是线程执行完 run()
方法以后,线程进入死亡状态,并不意味该线程的对象被回收,线程也能够再次被唤醒。
当正在运行线程调用 wait()
的时候,线程会丢失CPU时间分片,进入一个等待状态,等待其余线程调用 notify()/notifyAll()
的时候,将其唤醒。若是没有被唤醒,线程一直处于等待状态,也能够称之为假死状态。注意区别好线程调用 sleep()
进入睡眠状态,两者之间不尽相同,调用 sleep()
的语义是线程须要进入休眠状态,休眠时间是在已经被设定好的,时间到了以后会自行进入就绪状态等待操做系统调度。线程调用 wait()
的目的是拥有当前锁对象的线程不须要继续执行(释放锁,让出CPU时间分片,且不会马上进入就绪状态),进入等待队列的线程须要被 notify()/notifyAll()
唤醒。唤醒后,线程会进入锁池中排队去等待,直到拿到锁的权限,只有拿到锁的权限才能够进入就绪状态等待操做系统调度。
当咱们在调用 wait()
, notify()
, notifyAll()
方法的时候,编译时能够正常经过,可是运行期间可能会抛出异常 IllegalMonitorStateException
,这是为什么?在调用 wait()
语句的时候,前置条件是:当前线程必须拥有当前对象的锁的权限。一般状况下,咱们一般会说在调用 wait()
方法以前,必须先得到 synchronized
的对象锁【这个语句自己不对,可是能够很好说明问题】。
好比调用了线程的 join()
方法的时候,若是细心的同窗会去查看源码,会发如今其内部是经过调用 wait(0)
方法来达到让当前正在执行的线程进入等待状态,源码以下:
public final synchronized void join(long millis)
throws InterruptedException {
// ....
if (millis == 0) {
while (isAlive()) {
wait(0);// 能够考虑wait()与wait(timeout)的区别
}
}
// ....
}
当正在执行的线程遇到 synchronized
时,或者在等待队列中的线程被 notify()
, notifyAll()
唤醒的线程,会进入锁池中,直到拿到锁以后会进入就绪状态继续执行。 synchronized
关键字自没必要多说,线程同步原语,由内置语言实现。当一个线程已经进入这个锁,而且还未完成释放,其余的线程执行到这里,想要继续执行就须要进入锁池中排队等待当前的锁释放,直到获取锁(不必定是先到先得)才能够进入就绪状态。 前边提到,调用 wait()
方法须要先得到锁,它的语义更多的可能(猜想)是:当线程获取锁以后,在执行过程当中,因为某些条件不知足而选择主动进入等待状态,直到其余线程处理完一些事项后它才会被唤醒并被操做系统从新调度。因此,只有在等待队列中的线程才能够调用 notify()
, notifyAll()
来唤醒。
以上为笔者对于线程的生命周期的理解,如如有错误,请大侠指正!