热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java线程生命周期这样理解挺简单的

之前写过SpringBean生命周期三部曲:有朋友留言说:“了解了它们的生命周期后,使用SpringBean好比看到它们的行动轨迹,现在使用就一点都不慌了”。我和他一样,了解事物的生命周期目的

之前写过 Spring Bean 生命周期三部曲:

  1. Spring Bean生命周期之缘起
  2. Spring Bean生命周期之缘尽
  3. Spring Aware 到底是什么?

有朋友留言说:“了解了它们的生命周期后,使用 Spring Bean 好比看到它们的行动轨迹,现在使用就一点都不慌了”。我和他一样,了解事物的生命周期目的很简单,唯【不慌】也

Java线程生命周期这样理解挺简单的

Java 并发系列 已经写了很多,从来还没提起过那个它【Java线程生命周期】。有了前序理论图文的铺垫,在走进源码世界之前,谈论它的时机恰好到了。因为,编写并发程序的核心之一就是正确的摆弄线程状态

线程生命周期的几种状态

刚接触线程生命周期时,我总是记不住,也理解不了他们的状态,可以说是比较混乱,更别说它们之间是如何进行状态转换的了。原因是我把 操作系统通用线程状态编程语言封装后的线程状态 概念混淆在一起了

操作系统通用线程状态

个人觉得通用线程状态更符合我们的思考习惯。其状态总共有 5 种 (如下图)。对于经常写并发程序的同学来说,其嘴里经常念的都是操作系统中的这些通用线程状态,且看

Java线程生命周期这样理解挺简单的

除去生【初始状态】死【终止状态】,其实只是三种状态的各种转换,听到这句话是不是心情放松了很多呢?

为了更好的说明 通用线程状态Java 语言中的线程状态 ,这里还是先对前者进行简短的说明

初始状态

线程已被创建,但是还不被允许分配CPU执行。 注意,这个被创建其实是属于编程语言层面的,实际在操作系统里,真正的线程还没被创建 , 比如 Java 语言中的 new Thread()。

可运行状态

线程可以分配CPU执行, 这时,操作系统中线程已经被创建成功了

运行状态

操作系统会为 处在可运行状态的线程 分配CPU时间片,被 CPU 临幸后,处在可运行状态的线程就会变为运行状态

休眠状态

如果处在运行状态的线程调用 某个阻塞的API等待某个事件条件可用 ,那么线程就会转换到休眠状态, 注意:此时线程会释放CPU使用权,休眠的线程永远没有机会获得CPU使用权,只有当等待事件出现后,线程会从休眠状态转换到可运行状态

终止状态

线程执行完 或者 出现异常 (被interrupt那种不算的哈,后续会说)就会进入终止状态,正式走到生命的尽头,没有起死回生的机会

接下来就来看看你熟悉又陌生,面试又经常被问到的Java 线程生命周期吧

Java语言线程状态

在 Thread 的源码中,定义了一个枚举类 State,里面清晰明了的写了Java语言中线程的6种状态:

  1. NEW
  2. RUNNABLE
  3. BLOCKED
  4. WAITING
  5. TIMED_WAITING
  6. TERMINATED

Java线程生命周期这样理解挺简单的

这里要做一个小调查了,你有查看过这个类和读过其注释说明吗?(欢迎留言脚印哦)

Java线程生命周期这样理解挺简单的

耳边响起五环之歌,Java中线程状态竟然比通用线程状态的 5 种多1种,变成了 6 种。这个看似复杂,其实并不是你想的那样,Java在通用线程状态的基础上,有裁剪,也有丰富,整体来说是少一种。再来看个图,注意颜色区分哦

Java线程生命周期这样理解挺简单的

Java 语言中

  • 将通用线程状态的 可运行状态运行状态 合并为 Runnable
  • 休眠状态 细分为三种 ( BLOCKED / WAITING / TIMED_WAITING ); 反过来理解这句话,就是这三种状态在操作系统的眼中都是休眠状态,同样 不会获得CPU使用权

看上图右侧【Java语言中的线程状态】,进一步简洁的说,除去线程生死,我们只要玩转 RUNNABLE休眠状态 的转换就可以了,编写并发程序也多数是这两种状态的转换。所以我们需要了解,有哪些时机,会触发这些状态转换

远看看轮廓, 近看看细节。我们将上面Java语言中的图进行细化,将触发的节点放到图中 (这看似复杂的图,其实三句话就能分解的,所以别慌),且看:

Java线程生命周期这样理解挺简单的

RUNNABLE与BLOCKED状态转换

当且仅有(just only)一种情况会从 RUNNABLE 状态进入到 BLOCKED 状态,就是线程在等待 synchronized 内置隐式锁;如果等待的线程获取到了 synchronized 内置隐式锁,也就会从 BLOCKED 状态变为 RUNNABLE 状态了

注意:

上面提到,以操作系统通用状态来看,线程调用阻塞式 API,会变为休眠状态(释放CPU使用权),但在JVM层面,Java线程状态不会发生变化,也就是说Java线程的状态依旧会保持在 RUNNABLE 状态。JVM并不关心操作系统调度的状态。在JVM看来,等待CPU使用权(操作系统里是处在可执行状态)与等待I/O(操作系统是处在休眠状态),都是等待某个资源,所以都归入了RUNNABLE 状态

​ —— 摘自《Java并发编程实战》

RUNNABLE与WAITING状态转换

调用 不带时间参数的等待API ,就会从RUNNABLE状态进入到WAITING状态;当被唤醒就会从WAITING进入RUNNABLE状态

RUNNABLE与 TIMED-WAITING 状态转换

调用 带时间参数的等待API ,自然就从 RUNNABLE 状态进入 TIMED-WAITING 状态;当被唤醒或超时时间到就会从TIMED_WAITING进入RUNNABLE状态

看图中的转换 API 挺多的,其实不用担心,后续分析源码章节,自然就会记住的,现在有个印象以及知道状态转换的节点就好了

相信到这里,你看Java线程生命周期的眼神就没那么迷惑了,重点就是RUNNABLE与休眠状态的切换,接下来我们看一看,如何查看线程中的状态,以及具体的代码触发点

如何查看线程处在什么状态

程序中调用 getState() 方法

Thread 类中同样存在 getState() 方法用于查看当前线程状态,该方法就是返回上面提到的枚举类 State

Java线程生命周期这样理解挺简单的

NEW

就是上面提到, 编程语言中特有的,通过继承 Thread 或实现 Runnable 接口定义线程后,这时的状态都是 NEW

Thread thread = new Thread(() -> {});
System.out.println(thread.getState());

RUNNABLE

调用了 start() 方法之后,线程就处在 RUNNABLE 状态了

Thread thread = new Thread(() -> {});
thread.start();
//Thread.sleep(1000);
System.out.println(thread.getState());

BLOCKED

等待 synchronized 内置锁,就会处在 BLOCKED 状态

public class ThreadStateTest {

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new DemoThreadB());
		Thread t2 = new Thread(new DemoThreadB());

		t1.start();
		t2.start();

		Thread.sleep(1000);

		System.out.println((t2.getState()));
		System.exit(0);
	}
}

class DemoThreadB implements Runnable {
	@Override
	public void run() {
		commonResource();
	}

	public static synchronized void commonResource() {
		while(true) {
			
		}
	}
}

WAITING

调用线程的 join() 等方法,从 RUNNABLE 变为 WAITING 状态

public static void main(String[] args) throws InterruptedException {
		Thread main = Thread.currentThread();

		Thread thread2 = new Thread(() -> {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
        Thread.currentThread().interrupt();
				e.printStackTrace();
			}
			System.out.println(main.getState());
		});
		thread2.start();
		thread2.join();
	}

TIMED-WAITING

调用了 sleep(long) 等方法,线程从 RUNNABLE 变为 TIMED-WAITING 状态

public static void main(String[] args) throws InterruptedException {
		Thread thread3 = new Thread(() -> {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
        // 为什么要调用interrupt方法?
				Thread.currentThread().interrupt();
				e.printStackTrace();
			}
		});
		thread3.start();

		Thread.sleep(1000);
		System.out.println(thread3.getState());
	}

TERMINATED

线程执行完自然就到了 TERMINATED 状态了

Thread thread = new Thread(() -> {});
thread.start();
Thread.sleep(1000);
System.out.println(thread.getState());

以上是程序中查看线程,自己写写测试看看状态还好,现实中的程序怎么可能允许你加这么多无用代码,所以,翠花,上酸菜( jstack

Java线程生命周期这样理解挺简单的

jstack 命令查看

相信你听说过这玩意,jstack 命令就比较强大了,不仅能查看线程当前状态,还能看调用栈,锁等线程栈信息

大家可以随意写一些程序,这里我用了上面 WAITING 状态的代码, 修改睡眠时间 Thread.sleep(100000),然后在终端按照下图标示依次执行下图命令

Java线程生命周期这样理解挺简单的

更多功能还请大家自行查看,后续会单独写文章来教大家如何使用jstack查看线程栈信息

Arthas

这个利器,无须多言吧,线上找茬监控没毛病,希望你可以灵活使用这个工具,攻克疑难杂症

Java线程生命周期这样理解挺简单的

查看线程栈详细信息,非常方便: https://alibaba.github.io/arthas/thread.html

Java线程生命周期这样理解挺简单的

相信你已经和Arthas确认了眼神

Java线程生命周期这样理解挺简单的

关于线程生命周期状态整体就算说完了,编写并发程序时多问一问自己:

调用某个API会将你的线程置为甚么状态?

多问自己几次,自然就记住上面的图了

灵魂追问

  1. 为什么调用 Thread.sleep, catch异常后,调用了Thread.currentThread().interrupt();

  2. 进入 BLOCKED只有一种情况,就是等待 synchronized 监视器锁,那调用 JUC 中的 Lock.lock() 方法,如果某个线程等待这个锁,这个线程状态是什么呢?为什么?

    public class ThreadStateTest {
    
    	public static void main(String[] args) throws InterruptedException {
    		TestLock testLock = new TestLock();
    
    		Thread thread2 = new Thread(() -> {
    			testLock.myTestLock();
    		}, "thread2");
    
    		Thread thread1 = new Thread(() -> {
    				testLock.myTestLock();
    			}, "thread1");
    
    		thread1.start();
    		Thread.sleep(1000);
    
    		thread2.start();
    		Thread.sleep(1000);
    
    		System.out.println("****" + (thread2.getState()));
    
    		Thread.sleep(20000);
    	}
    }
    
    @Slf4j
    class TestLock{
    	private final Lock lock = new ReentrantLock();
    
    	public void myTestLock(){
    		lock.lock();
    		try{
    			Thread.sleep(10000);
    			log.info("testLock status");
    		} catch (InterruptedException e) {
    			log.error(e.getMessage());
    		} finally {
    			lock.unlock();
    		}
    	}
    }
    
  1. synchronized 和 Lock 有什么区别?

我这面也在逐步总结常见的并发面试问题(总结ing……)答案整理好后会通知大家,请持续关注

Java线程生命周期这样理解挺简单的

同时,这里也整理了一点 Java 硬核资料,有需要的就公众号回复【资料】/【666】吧

Java线程生命周期这样理解挺简单的


以上所述就是小编给大家介绍的《Java线程生命周期这样理解挺简单的》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 我们 的支持!


推荐阅读
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
author-avatar
大帅哥鹏飞lyll
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有