【多线程学习笔记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 改变线程优先级


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


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

相关文章
|
6天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
14天前
|
Java Spring
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
77 10
spring多线程实现+合理设置最大线程数和核心线程数
|
22天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
38 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
8天前
|
Python
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
|
24天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
38 10
|
5天前
|
NoSQL 网络协议 Unix
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
16 1
|
6天前
|
Java
COMATE插件实现使用线程池高级并发模型简化多线程编程
本文介绍了COMATE插件的使用,该插件通过线程池实现高级并发模型,简化了多线程编程的过程,并提供了生成结果和代码参考。
|
2月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
64 1
|
1月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。