【多线程学习笔记3】线程的生命周期与控制

简介: 【多线程学习笔记3】线程的生命周期与控制

1 线程的生命周期



当线程被创建并启动以后,它既不是一启动就进入执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直霸占着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、就绪之间切换。


1.1 新建和就绪状态


新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由JVM为其分配内存,并初始化其成员变量的值。


就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态,JVM会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于何时运行,取决于JVM里线程调度器的调度。


注意:


(1)启动线程使用start()方法,而不是run()方法!永远不要调用线程对象的run()方法!调用start()方法来启动线程,系统会把对应的run()方法当做线程执行体来处理,但如果直接调用线程对象的run()方法,则run()方法立即就会执行,而且在run()方法返回之前其他线程无法并发执行。简而言之,就是启动线程的正确方法是调用Thread对象的start()方法,而不是直接调用run()方法,否则就不是新开线程了,而是在同步单线执行了。


(2)只能对处于新建状态的线程调用start()方法,否则将引发IllegalThreadStateException异常。


1.2 运行和阻塞状态


运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。


当一个线程开始运行后,它很难一直处于运行状态,线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。说明对于抢占式策略,在选择下一个线程时,系统会考虑线程的优先级。


所有现代的桌面和服务器操作系统都采用抢占式调度策略,但一些小型设备比如手机则可能采用协作式调度策略,在这样的系统中,只有当一个线程调用了它的sleep()或yield()方法后才会放弃所占用的资源-也就是必须由该线程主动放弃所占用的资源。

当发生如下情况时,线程将会主动进入阻塞状态:


(1)线程调用sleep()方法主动放弃所占用的处理器资源。


(2)线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。


(3)线程试图获得一个同步锁,但该同步锁被其他线程所持有。


(4)线程在等待某个通知(notify)。


(5)程序调用了线程的suspend()方法将线程挂起。(容易引起死锁,应尽量避免使用!)


针对上面的几种情况,就当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态:


(1)调用sleep()方法的线程经过了指定的时间。


(2)线程调用的阻塞式IO方法已经返回。


(3)线程成功获得了同步锁。


(4)线程正在等待某个通知时,其他线程发出了一个通知。


(5)处于挂起状态的线程被调用了resume()恢复方法。


网络异常,图片无法展示
|


线程状态转换图


从上图中也可以看到,线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。而就绪和运行状态之间的转换通常不受程序控制,而是由系统线程调度所决定,当处于就绪状态的线程获得处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器资源时,该线程进入就绪状态。但有一个方法例外,调用yield()方法可以让运行状态的线程转入就绪状态。


1.3 线程死亡


以下三种方式结束,线程即进入死亡状态:


(1)run()或call()方法执行完成,线程正常结束。


(2)线程抛出一个未捕获的Exception或Error。


(3)直接调用该线程的stop()方法来结束该线程,该方法容易导致死锁,不推荐使用。


注意:不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,该线程将不可再次作为线程执行,否则将引发IllegalThreadStateException异常。


2 控制线程



2.1 join线程


join()方法:让一个线程等待另一个线程完成。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。


2.2 后台线程


后台线程也叫守护线程,JVM的垃圾回收线程就是典型的后台线程。后台线程有个特征,如果所有的前台线程都死亡,后台线程也会自动死亡。通过调用Thread对象的setDaemon(true)方法可以将指定线程设置成后台线程,同时Thread类还提供了一个isDaemon()方法用于判断执行的线程是否为后台线程。


注意:将一个线程设置为后台线程,必须要在该线程启动之前设置,也就是说,setDaemon(true)方法必须在start()方法之前调用,否则将引发IllegalThreadStateException异常。


2.3 线程睡眠:sleep


线程调用sleep()方法进入阻塞状态,在其睡眠时间段内,该线程不会获得执行机会,即便系统中没有其他可执行的线程,处于sleep()中的线程也不会执行。


2.4 线程让步:yield


yield()方法可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。yield()只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是,当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。


实际上,当某个线程调用yield()方法之后,只有优先级与当前线程相同或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。在多CPU并行的环境下,yield()方法的功能很多时候并不明显。


2.5 改变线程优先级


每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。每个线程默认的优先级都与创建它的父线程的优先级相同。


注意:由于不同操作系统的支持不同,应该尽量避免直接为线程指定优先级,而应该使用静态常量来设置优先级,这样可以保证程序具有最好的可移植性。

相关文章
|
1月前
|
Java API 调度
Java 线程的生命周期
在JDK 1.5之前,线程的生命周期包括五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。JDK 1.5及之后增加了三种阻塞状态,共六种状态:新建、可运行、终止、锁阻塞、计时等待和无限等待。这些状态描述了线程在操作系统和JVM中的不同阶段。
Java 线程的生命周期
|
2月前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
41 1
[Java]线程生命周期与线程通信
|
23天前
线程的生命周期
线程的生命周期包括以下状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。 线程控制: sleep():使线程暂停指定时间。 join():等待线程终止。 yield():让出CPU执行权给其他线程。
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
27 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
23 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
38 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
44 1
|
2月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
48 1
|
29天前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
61 0
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
58 1
C++ 多线程之初识多线程