06_线程的生命周期及状态
【线程状态】
在线程的生命周期中,它要经过 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Bolcked)、死亡(Dead)总共5种状态。
尤其在线程启动之后,它不可能一直占着CPU运行,所以CPU需要在多个线程之间相互切换,于是线程的状态也会多次在运行、阻塞之间切换。
【新建和就绪状态】
新建状态:当程序使用new关键字创建了一个线程之后,这个线程就处于新建状态,此时,它和一般的java对象没有区别,仅仅由java虚拟机为其分配内存,并初始化其成员变量值。此时的线程对象没有表现出任何线程的动态特性,程序也不会执行线程的执行体。
就绪状态:当线程对象调用了start()方法之后,该线程就处于就绪状态,就绪状态相当于"等待执行"。java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示线程可以运行,但是进入运行状态取决于JVM里线程调度器的调度。如下情况会进入就绪状态;
1.调用sleep()方法到了指定的时间。
2.线程调用的阻塞式IO方法已经返回。
3.线程成功地获得了试图取得的同步监视器。
4.线程正在等待某个通知时,其它线程发出了一个通知。
5.处于挂起状态的线程被调用了resume()恢复方法。
[ 注意 ]
启动线程使用 start()方法,不是run()方法!!永远不要调用线程对象的run()方法!!!
调用start()方法,系统会把run()方法当成线程执行体来处理。
直接调用run()方法,则run()方法会立即被执行,系统会把线程对象当成一个普通对象来处理,run()方法也变成了一个普通的方法。
另外,直接调用线程对象的run()方法,则run()方法内不能通过getName()来获得当前执行线程的名字,而是需要使用Thread.currentThread先获得当前线程,再调用线程的getName()方法来获得线程的名字。
另外,调用了线程的run()方法之后,该线程已经不再处于新建状态,不要再次调用线程对象的start()方法,只能对处于新建状态的线程调用start()方法,否则会引发IllegalThreadStateException异常。
[ 小技巧 ]
如果希望某一线程对象在调用了start()方法后立即执行,可以使用Thread.sleep(1)让当前执行的其它线程(如主线程)睡眠1毫秒,在这1毫秒内,CPU不会空闲,它会立即去执行处于就绪状态的线程,这样就可以让某一线程立即开始执行。
【运行和阻塞状态】
运行状态:处于就绪状态的线程获得了CPU的执行权,开始执行run()方法的线程执行体,则该线程处于运行状态。
阻塞状态:发生以下几种情况,线程会进入阻塞状态:
1.线程调用了sleep()方法,主动放弃所占用的系统资源。
2.线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
3.线程试图获得一个同步同步监视器,但该同步监视器被其它线程所持有。
4.线程正在等待某个通知(notify或notifyAll)。
5.程序调用了线程的suspend()方法将该线程挂起,但该方法会导致死锁,要避免使用。
[ 关于抢占式策略 ]
当一个线程开始运行之后,它不会一直处于运行状态,线程在运行过程中需要被中断,目的是使其它的线程获得执行机会,线程调度的细节取决于底层所用策略。对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务,当该时间段用完之后,系统会让该线程进入就绪状态,让所有线程抢占接下来的执行机会,当然,系统会考虑线程的优先级。
当前正在执行的线程被阻塞之后,其它线程也可以获得执行机会,被阻塞的状态在合适的时候也会进入就绪状态(注意是就绪状态,NOT执行状态),即被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它。
阻塞——>就绪——>运行
[ 线程状态转换图 ]
从图中可以看出,线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。
而就绪状态和运行状态之间的转换通常不受程序控制,而是由系统线程调度所决定,处于就绪状态的线程后的处理器的执行权后,线程进入运行状态。
当处于运行状态的线程失去处理器资源时,该线程进入就绪状态,有一个方法例外,即yield()方法可以让运行状态直接转入就绪状态。
【线程死亡】
线程在三种情况下会进入死亡状态:
1.run()方法或call()方法执行结束后,线程正常结束死亡。
2.线程抛出一个未捕获的Exception或Error。
3.直接调用该线程的stop()方法来结束该线程,但是stop()方法容易导致死锁,最好不要用。
[ 提示 ]
测试某个线程是否已经死亡,可以调用对象的isAlive()方法,
当线程处于就绪、运行、阻塞三种状态时,返回true。
当线程处于新建、死亡状态时,返回false。
[ 注意 ]
不要试图对一个已经死亡的线程调用start()方法使它重启,死亡就是死亡,该线程不可再次作为线程执行,否则会抛出IllegalThreadStateException异常,
对新建状态的线程两次调用start()方法也是错误的额,都会引发IllegalThreadStateException异常。