一图全面了解Java线程的生命周期

前言

    线程的“生老病死”,我们称之为生命周期。
    那java线程的生命周期都有哪些个阶段呢?请看下图(图片来源于网络):
在这里插入图片描述

    重点是各个状态直接的转化在java代码层面上如何体现,你需要好好理解。

new(初始化状态)

    新建一个线程对象。

    例如下面代码,new了一个MyThread的对象t1,那么t1线程就是初始化状态。

// 自定义一个线程类
class MyThread extends Thread {
  public void run() {
    // 省略业务代码
    ......
  }
}
// 创建线程对象
MyThread t1= new MyThread();



runnable(可运行(就绪)/ 运行状态)

    在java线程层面上,runnable状态包括可运行(也叫就绪)状态和运行状态。

  • 可运行转态(runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  • 运行状态(Running):就绪状态的线程获得CPU并执行程序代码。

    在代码上体现:

// 从new状态转换到runnable状态
myThread.start();



blocked(阻塞状态)

    runnable状态到blocked状态,只有一种场景:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中,线程进入阻塞状态。阻塞的线程放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。


waiting(无时限等待)

    有三种场景会导致线程从runnable到waiting

  • 第一种场景,获得 synchronized 隐式锁的线程,调用无参数的 Object.wait() 方法,调用该方法的线程进入waiting状态。当其他的线程获得对应对象锁之后,调用notify或notifyall方法,waiting的线程才转换到runnable状态。

notify和notifyAll的区别:
    notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
notifyAll会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll方法。


sleep和wait的区别:

  • 当线程执行sleep方法时,不会释放当前的锁(如果当前线程进入了同步锁),也不会让出CPU。sleep(milliseconds)可以用指定时间使它自动唤醒过来,如果时间不到只能调用interrupt方法强行打断。

  • 当线程执行wait方法时,会释放当前的锁,然后让出CPU,进入等待状态。只有当notify/notifyAll被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait() ,再次释放锁。

  • Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”

  • notify/notifyAll的执行只是唤醒沉睡的线程,而不会立即释放锁,必须执行完notify()方法所在的synchronized代码块后才释放。所以在编程中,尽量在使用了notify/notifyAll()后立即退出临界区。

  • 第二种场景,调用无参数的 Thread.join() 方法。其中的 join() 是一种线程同步方法,例如有一个线程对象 thread A,当调用 A.join() 的时候,执行这条语句的线程会等待 thread A 执行完,而等待中的这个线程,其状态会从 runnable转换到 waiting。当线程 thread A 执行完,原来等待它的线程又会从 waiting状态转换到 runnable。

  • 第三种场景,调用 LockSupport.park() 方法。Java 并发包中的锁,都是基于LockSupport实现的。调用 > > LockSupport.park() 方法,当前线程会阻塞,线程的状态会从 runnable转换到 WAITING。调用 LockSupport.unpark(Thread thread) 可唤醒目标线程,目标线程的状态又会从 waiting状态转换到 runnable。

park()与unpark():
    concurrent包是基于AQS(AbstractQueuedSynchronizer)框架的,AQS框架借助于两个类:

  • Unsafe(提供CAS操作)
  • LockSupport(提供park/unpark操作)

    LockSupport.park()和LockSupport.unpark(Thread thread)调用的是Unsafe中的native代码。

    park与unpark的特点
        park/unpark的设计原理核心是“许可”(permit):park是等待一个许可,unpark是为某线程提供一个许可。permit不能叠加,也就是说permit的个数要么是0,要么是1。也就是不管连续调用多少次unpark,permit也是1个。线程调用一次park就会消耗掉permit,再一次调用park又会阻塞住。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
        unpark可以先于park调用。在使用park和unpark的时候可以不用担心park的时序问题造成死锁。相比之下,wait/notify存在时序问题,wait必须在notify调用之前调用,否则虽然另一个线程调用了notify,但是由于在wait之前调用了,wait感知不到,就造成wait永远在阻塞。
        park和unpark调用的时候不需要获取同步锁。

    park与unpark的优点
        与Object类的wait/notify机制相比,park/unpark有两个优点:
  • 以thread为操作对象更符合阻塞线程的直观定义。
  • 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

    底层实现原理
        在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。
    mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。



timed_waiting(有时限等待)

    有五种场景会触发这种转换:

  • 调用带超时参数的 Thread.sleep(long millis) 方法
  • 获得 synchronized 隐式锁的线程,调用带超时参数的 Object.wait(long timeout) 方法
  • 调用带超时参数的 Thread.join(long millis) 方法
  • 调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法
  • 调用带超时参数的 LockSupport.parkUntil(long deadline) 方法



terminated(终止状态)

    线程执行完 run() 方法后或者执行run() 方法的时候异常抛出,会转换到 terminated状态。
    有时候我们需要强制中断 run() 方法的执行,例如 run() 方法访问一个很慢的网络,我们等不下去了,想终止怎么办呢?Java 的 Thread 类里面倒是有个stop()方法,不过已经标记为@Deprecated,所以不建议使用了。正确的姿势其实是调用**interrupt()**方法。

多线程连载(系列文章按顺序阅读哦):
Java内存模型-volatile的应用(实例讲解)
synchronized的三种应用方式(实例讲解)
可重入锁-synchronized是可重入锁吗?
大彻大悟synchronized原理,锁的升级
一文弄懂Java的线程池
公平锁和非公平锁-ReentrantLock是如何实现公平、非公平的

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值