16.3 线程的生命周期 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
线程的5种状态 在线程的生命周期中,它要经过:
新建(New
)、
就绪(Runnable
)、
运行(Running
)、
阻塞(Blocked
)
死亡(Dead
)
这5种状态。
尤其是当线程启动以后,它不可能一直霸占着CPU
独自运行,所以CPU
需要在多条线程之间切换,于是线程状态也会多次在运行
、阻塞
之间切换。
16.3.1 新建状态和就绪状态 新建状态 new Thread创建线程后 当程序使用new
关键字创建了一个线程之后,该线程就处于新建状态
,此时它和其他的Java
对象一样,仅仅由Java
虚拟机为其分配内存,并初始化其成员变量的值。 此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。
就绪状态 调用Thread对象start方法后 当线程对象调用了start
方法之后,该线程处于就绪状态
,Java
虚拟机会为其创建方法调用栈 和程序计数器 ,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了 。至于该线程何时开始运行,取决于JVM
里线程调度器的调度。
启动线程使用start方法 永远不要调用线程对象的run方法 启动线程使用start()
方法,而不是run()
方法 !永远不要调用线程对象的run()
方法!
调用start()
方法来启动线程,系统会把该run()
方法当成线程执行体 来处理;
如果直接调用线程对象的run()
方法,系统把线程对象当成一个普通对象,把run()
方法当成一个普通方法,而不是线程执行体,run()
方法将立即就会被执行,而且在run()
方法返回之前其他线程无法并发执行
程序 调用run方法不会启动线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class InvokeRun extends Thread { private int i ; public void run () { for ( ; i < 100 ; i++ ) { System.out.println(Thread.currentThread().getName() + " " + i); } } public static void main (String[] args) { for (int i = 0 ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 20 ) { new InvokeRun ().run(); new InvokeRun ().run(); } } } }
运行效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 main 0 main 1 main 2 main 3 ...... main 18 main 19 main 20 main 0 main 1 main 2 ...... main 44 main 45 main 46 main 47 main 48 main 49 main 0 main 1 main 2 main 3 ...... main 47 main 48 main 49
上面程序创建线程对象后**直接调用了线程对象的run()
方法,程序运行的结果是整个程序只有一个线程:主线程
**。 还有一点需要指出,如果直接调用线程对象的run()
方法,则run()
方法里不能直接通过getName()
方法来获得当前执行线程的名字,而是需要使用Thread.currentThread()
方法先获得当前线程,再调用线程对象的getName()
方法来获得线程的名字。 通过上面程序不难看出,启动线程的正确方法是调用Thread
对象的start()
方法,而不是直接调用run()
方法 ,否则就变成单线程程序了
只能对处于新建状态的线程调用start方法 只能对处于新建状态的线程调用start
方法,否则将引发IllegalThreadStateException
异常 。调用了线程的run()
方法之后,该线程已经不再处于新建状态,不要再次调用线程对象的start()
方法。 调用线程对象的start
方法之后,该线程立即进入就绪状态,就绪状态相当于”等待执行”,但该线程并未真正进入运行状态。
如何让子线程立即执行 如果希望调用子线程的start
方法后子线程立即开始执行,程序可以使用Thread.sleep(1)
来让当前运行的线程(主线程)睡眠1毫秒
——1毫秒就够了,因为在这1毫秒内CPU
不会空闲,它会去执行另一个处于就绪状态的线程,这样就可以让子线程立即开始执行。
小结
当程序使用new
关键字创建了一个线程之后,该线程就处于新建状态
当线程对象调用了start
方法之后,该线程处于就绪状态
,就绪状态相当于”等待执行”状态,此时该线程并未真正进入运行状态。
只能对处于新建状态的线程调用start
方法,否则将引发IllegalThreadStateException
异常
启动线程的正确方法是调用Thread
对象的start()
方法,而不是直接调用run()
方法 ,直接调用了线程对象的run()
方法,程序运行的结果是整个程序只有一个线程:主线程
如果希望调用子线程的start
方法后子线程立即开始执行,则可以让当前运行的线程(主线程)睡眠1毫秒
,这种情况针只有一个子线程的情况,个人觉得用处不大.
16.3.2 运行状态和阻塞状态 运行状态 run方法得到执行 如果处于就绪状态的线程获得了CPU
,开始执行线程的执行体run()
方法,则该线程处于运行状态
,如果计算机只有一个CPU
,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程**并行
执行**;不过当线程数大于处理器数时,依然会存在多个线程在同一个CPU
上轮换的现象。(注意多处理器的机器上是并行:parallel
,单处理器上是并发
) 当一个线程开始运行后,它不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了),线程在运行过程中需要被中断,目的是使其他线程获得执行的机会 ,线程调度的细节取决于底层平台所采用的策略。对于采用抢占式策略
的系统而言,系统会给每个可执行的线程一个小时间段
来处理任务;当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。 所有现代的桌面和服务器操作系统都采用抢占式调度策略
,但一些小型设备如手机
则可能采用协作式调度策略
,在这样的系统中,只有当一个线程调用了它的sleep()
或yield()
方法后才会放弃所占用的资源,也就是必须由该线程主动放弃所占用的资源。
阻塞状态 sleep IO阻塞 等待同步锁 等待通知 suspend 当发生如下情况时,线程将会进入阻塞状态。
线程调用sleep()
方法主动放弃所占用的处理器资源。
线程调用了一个阻塞式IO方法
,在阻塞式IO方法
返回之前,该线程被阻塞。
线程试图获得一个同步监视器 ,但该同步监视器正被其他线程所持有的时候
。
线程在等待某个通知(notify
)。
程序调用了线程的suspend()
方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。
阻塞结束进入就绪 当前正在执行的线程被阻塞之后,其他线程就可以获得执行的机会。被阻塞的线程会在合适的时候重新进入就绪状态
,注意是就绪状态
而不是运行状态 。也就是说,被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它 。
阻塞进入就绪 sleep时间到 IO方法返回 获得同步锁 收到通知 resumed 针对上面几种情况,当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态。
调用sleep
方法的线程经过了指定时间
线程调用的阻塞式IO方法
已经返回。
线程成功地获得了试图取得的同步监视器。
线程正在等待某个通知时,其他线程发出了一个通知
。
处于挂起状态的线程被调用了resumed()
恢复方法。
线程状态转换图 图16.4显示了线程状态转换图。 从图16.4中可以看出:
线程从阻塞状态
只能进入就绪状态
,无法直接进入运行状态 。
就绪状态
和运行状态
之间的转换通常不受程序控制,而是由系统线程调度所决定 ,
当处于就绪状态
的线程获得处理器资源时,该线程进入运行状态
;
当处于运行状态
的线程失去处理器资源时,该线程进入就绪状态
。
但有个方法例外,**调用yield()
方法可以让运行状态
的线程转入就绪状态
**。
16.3.3 线程死亡 线程死亡 run方法结束 异常 stop 线程会以如下三种方式结束,结束后就处于死亡状态。
run()
或call()
方法执行完成,线程正常结束。
线程抛出一个未捕获的Exception
或Error
。
直接调用该线程的stop()
方法来结束该线程,不过stop()
方法容易导致死锁,通常不推荐使用。
一个线程结束不会影响其他线程 当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受主线程的影响。
isAlive方法 为了测试某个线程是否已经死亡,可以调用线程对象的isAlive()
方法,
当线程处于就绪
、运行
、阻塞
三种状态时,isAlive()
方法将返回true
;
当线程处于新建
、死亡
两种状态时,isAlive()
方法将返回false
已经死亡的线程无法再次启动 不要试图对一个已经死亡的线程调用start
方法使它重新启动,死亡就是死亡,该线程将不可再次作为线程执行。
程序 死亡的线程无法再次start 下面程序尝试对处于死亡状态的线程再次调用start
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class StartDead extends Thread { private int i ; public void run () { for ( ; i < 100 ; i++ ) { System.out.println(getName() + " " + i); } } public static void main (String[] args) { StartDead sd = new StartDead (); for (int i = 0 ; i < 300 ; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 20 ) { sd.start(); System.out.println(sd.isAlive()); } if (i > 20 && !sd.isAlive()) { sd.start(); } } } }
上面程序中试图在线程已死亡的情况下再次调用start
方法来启动该线程。 运行上面程序,将引发IllegalThreadStateException
异常。这表明处于死亡状态的线程无法再次运行了 。
只能对新建的线程start一次 不要对处于死亡状态的线程调用start
方法,只能对新建状态
的线程调用start
方法 ,对新建状态
的线程两次调用start
方法也是错误的 。这都会引发IllegalThreadStateException
异常.