这么理解线程生命周期,是不是很简单?

开发 后端
了解了它们的生命周期后,使用 Spring Bean 好比看到它们的行动轨迹,现在使用就一点都不慌了。

为什么要了解线程的生命周期?

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

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

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

[[320328]]

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

线程生命周期的几种状态

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

操作系统通用线程状态

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

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

为了更好的说明通用线程状态和 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

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

[[320330]]

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

Java 语言中

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

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

远看看轮廓, 近看看细节。我们将上面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

NEW

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

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

RUNNABLE

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

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

BLOCKED

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

  1. public class ThreadStateTest { 
  2.     public static void main(String[] args) throws InterruptedException {  
  3.         Thread t1 = new Thread(new DemoThreadB());  
  4.         Thread t2 = new Thread(new DemoThreadB());  
  5.         t1.start();  
  6.         t2.start(); 
  7.         Thread.sleep(1000);  
  8.         System.out.println((t2.getState()));  
  9.         System.exit(0); 
  10.     }  
  11.  
  12. class DemoThreadB implements Runnable {  
  13.     @Override  
  14.     public void run() {  
  15.         commonResource();  
  16.     }  
  17.     public static synchronized void commonResource() {  
  18.         while(true) {     
  19.          }  
  20.     }  

WAITING

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

  1. public static void main(String[] args) throws InterruptedException {  
  2.         Thread main = Thread.currentThread();  
  3.         Thread thread2 = new Thread(() -> {  
  4.             try {  
  5.                 Thread.sleep(1000);  
  6.             } catch (InterruptedException e) {  
  7.         Thread.currentThread().interrupt();  
  8.                 e.printStackTrace();  
  9.             }  
  10.             System.out.println(main.getState());  
  11.         });  
  12.         thread2.start();  
  13.         thread2.join();  
  14.     } 

TIMED-WAITING

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

  1. public static void main(String[] args) throws InterruptedException {  
  2.         Thread thread3 = new Thread(() -> {  
  3.             try {  
  4.                 Thread.sleep(3000);  
  5.             } catch (InterruptedException e) {  
  6.         // 为什么要调用interrupt方法?  
  7.                 Thread.currentThread().interrupt();  
  8.                 e.printStackTrace();  
  9.             }  
  10.         });  
  11.         thread3.start();  
  12.         Thread.sleep(1000);  
  13.         System.out.println(thread3.getState());  
  14.     } 

TERMINATED

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

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

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

[[320331]]

jstack 命令查看

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

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

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

Arthas

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

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

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

[[320332]]

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

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

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

灵魂追问

  1.  为什么调用 Thread.sleep, catch异常后,调用了Thread.currentThread().interrupt();
  2.  进入 BLOCKED只有一种情况,就是等待 synchronized 监视器锁,那调用 JUC 中的 Lock.lock() 方法,如果某个线程等待这个锁,这个线程状态是什么呢?为什么?   
  1. public class ThreadStateTest {  
  2.        public static void main(String[] args) throws InterruptedException {  
  3.            TestLock testLock = new TestLock();  
  4.            Thread thread2 = new Thread(() -> {  
  5.                testLock.myTestLock();  
  6.            }, "thread2");  
  7.            Thread thread1 = new Thread(() -> {  
  8.                    testLock.myTestLock();  
  9.                }, "thread1");  
  10.            thread1.start();  
  11.            Thread.sleep(1000);  
  12.            thread2.start();  
  13.            Thread.sleep(1000);  
  14.            System.out.println("****" + (thread2.getState()));  
  15.            Thread.sleep(20000); 
  16.        }  
  17.    }  
  18.    @Slf4j  
  19.    class TestLock{  
  20.        private final Lock lock = new ReentrantLock();  
  21.        public void myTestLock(){  
  22.            lock.lock();  
  23.            try{  
  24.                Thread.sleep(10000);  
  25.                log.info("testLock status");  
  26.            } catch (InterruptedException e) {  
  27.                log.error(e.getMessage());  
  28.            } finally {  
  29.                lock.unlock();  
  30.            }  
  31.        }  
  32.    } 

      3.   synchronized 和 Lock 有什么区别?

参考

感谢前辈们总结的精华,自己所写的并发系列好多都参考了以下资料

  •  Java 并发编程实战
  •  Java 并发编程之美
  •  码出高效
  •  Java 并发编程的艺术
  •  ......

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

 

 

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2009-06-24 10:47:55

JSF生命周期

2022-04-19 07:20:24

软件开发安全生命周期SSDLC应用安全

2010-07-14 10:48:37

Perl线程

2009-06-18 13:32:39

Java线程生命周期

2012-01-16 09:00:56

线程

2009-06-29 18:03:15

Java多线程线程的生命周期

2010-07-14 10:59:15

Perl线程

2023-10-26 08:25:35

Java线程周期

2015-07-08 16:28:23

weak生命周期

2021-08-24 07:53:28

AndroidActivity生命周期

2013-08-19 17:03:00

.Net生命周期对象

2012-06-20 10:29:16

敏捷开发

2021-07-19 05:52:29

网络生命周期网络框架

2009-06-11 11:28:35

JSF生命周期

2023-10-07 00:05:07

2013-08-01 09:40:51

Windows 8.1

2014-07-16 13:39:30

Windows Pho

2012-04-28 13:23:12

Java生命周期

2011-06-16 09:31:21

ActivityAndroid

2023-09-12 10:52:16

OpenSSL开源套件
点赞
收藏

51CTO技术栈公众号