Java并发编程实战
王宝令
资深架构师
立即订阅
15113 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

09 | Java线程(上):Java线程的生命周期

王宝令 2019-03-19
在 Java 领域,实现并发程序的主要手段就是多线程。线程是操作系统里的一个概念,虽然各种不同的开发语言如 Java、C# 等都对其进行了封装,但是万变不离操作系统。Java 语言里的线程本质上就是操作系统的线程,它们是一一对应的。
在操作系统层面,线程也有“生老病死”,专业的说法叫有生命周期。对于有生命周期的事物,要学好它,思路非常简单,只要能搞懂生命周期中各个节点的状态转换机制就可以了。
虽然不同的开发语言对于操作系统线程进行了不同的封装,但是对于线程的生命周期这部分,基本上是雷同的。所以,我们可以先来了解一下通用的线程生命周期模型,这部分内容也适用于很多其他编程语言;然后再详细有针对性地学习一下 Java 中线程的生命周期。

通用的线程生命周期

通用的线程生命周期基本上可以用下图这个“五态模型”来描述。这五态分别是:初始状态、可运行状态、运行状态、休眠状态终止状态
通用线程状态转换图——五态模型
这“五态模型”的详细情况如下所示。
初始状态,指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层面,真正的线程还没有创建。
可运行状态,指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程,被分配到 CPU 的线程的状态就转换成了运行状态
运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。
线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(73)

  • 姜戈
    可能出现无限循环,线程在sleep期间被打断了,抛出一个InterruptedException异常,try catch捕捉此异常,应该重置一下中断标示,因为抛出异常后,中断标示会自动清除掉!
    Thread th = Thread.currentThread();
    while(true) {
      if(th.isInterrupted()) {
        break;
      }
      // 省略业务代码无数
      try {
        Thread.sleep(100);
      }catch (InterruptedException e){
        Thread.currentThread().interrupt();
        e.printStackTrace();
      }
    }

    作者回复: 👍👍👍

    2019-03-19
    3
    153
  • Geek_961eed
    希望作者讲解一下每一期的思考题!
    2019-03-19
    55
  • 虎虎❤️
    我的一位长辈曾告诉我,没有真正学不会的知识或者技术,只是缺乏好的老师。

    有的人可以把复杂的知识讲明白,但是讲解的过程却也是晦涩难懂,不免落了下成。

    而学习王老师的课,我一直都觉得很轻松。云淡风轻地就把并发知识抽丝剥茧,确是更显功力。另一方面,我觉得人的大脑更喜欢接受这些平易近人的文字。看似浅近的文字,却更能带领我深入的思考,留下更深刻的印象。反观一些看起来高端大气上档次的论述,让人觉得云山雾罩,好不容易看懂了,但看过后却什么也想不起来了。大概是读文章的时候脑细胞都用来和晦涩的文字做斗争了,已经没有空间去思考和记忆了。

    再次感谢王老师给大家带来优秀的课程。

    作者回复: 看来我没必要写的很装了😂

    2019-03-19
    2
    50
  • Tristan
    为什么实战高并发程序设计医术中写道“Tread.stop()方法在结束线程时,会直接终止线程,并且会释放这个线程所持有的锁”,而您文中所写的“果线程持有 synchronized 隐式锁,也不会释放”??

    作者回复: 是我的错,我确认了一下,隐式锁可以释放。多谢多谢!!!

    2019-04-14
    2
    32
  • thas
    interrupt是中断的意思,在单片机开发领域,用于接收特定的事件,从而执行后续的操作。Java线程中,(通常)使用interrupt作为线程退出的通知事件,告知线程可以结束了。
    interrupt不会结束线程的运行,在抛出InterruptedException后会清除中断标志(代表可以接收下一个中断信号了),所以我想,interrupt应该也是可以类似单片机一样作为一种通知信号的,只是实现通知的话,Java有其他更好的选择。
    因InterruptedException退出同步代码块会释放当前线程持有的锁,所以相比外部强制stop是安全的(已手动测试)。sleep、join等会抛出InterruptedException的操作会立即抛出异常,wait在被唤醒之后才会抛出异常(就像阻塞一样,不被打扰)。
    另外,感谢老师提醒,I/O阻塞在Java中是可运行状态,并发包中的lock是等待状态。

    作者回复: 能和硬件中断联系起来👍👍👍

    2019-03-19
    1
    20
  • Junzi
    当发起中断之后,Thread.sleep(100);会抛出InterruptedException异常,而这个抛出这个异常会清除当前线程的中断标识,导致th.isInterrupted()一直都是返回false的。

    InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

    作者回复: 👍

    2019-03-26
    13
  • alias cd=rm -rf
    思考题,不能中断循环,异常捕获要放在while循环外面

    作者回复: 你这也是个办法

    2019-03-19
    2
    8
  • 缪文@场景鹿
    if(th.isInterrupted()) {
        break;
      }
    其实这段代码完全没必要啊,在捕获中断异常后,直接break就好了
    2019-04-02
    3
    7
  • 刘晓林
    感谢老师提醒,原来jvm层面的线程状态和os层面上的线程状态是不一样的,i/o挂起在jvm也是runable状态。另外并发包的lock其实是处于waitting状态。
    但是有个疑问,jvm中blocked状态的线程和waitting状态的线程,除了处在不同的队列之外,还有没有什么区别呀?我这里问的区别包括jvm和os两个层面,谢谢老师

    作者回复: block不能响应中断,os里应该都是休眠状态,因为都不能获得cpu使用权

    2019-03-19
    7
  • 老师 stop方法直接杀掉线程了,什么不会释放锁呢

    作者回复: 我也不知道搞jvm的人咋想的

    2019-03-19
    1
    7
  • rock
    new, runnable, block, waiting, time_waiting, terminated

    runnable-->block, 仅线程等待synchronized隐式锁
    block-->runnable, 线程获得了synchronized隐式锁

    runnable-->waiting, 线程获得了synchronized隐式锁,在临界区类调用wait(),(无时间限制的)等待指定的条件满足。
                         线程调用子线程的Thread.jion(),(无时间限制的)等待子线程执行完成返回。
                         线程调用LockSupport.park(),(无时间限制的)等待LockSupport.unPark(thead)。【LockSupport.park(),用于创建锁和其他同步类的底层基本线程阻塞原语,使当前线程block from thread scheduling】
    waiting-->runnable, 上述3种,条件达成后。

    runnable-->time_waiting, 线程获得了synchronized隐式锁,在临界区类调用wait(time),(有时间限制的)等待指定的条件满足。
                             线程调用子线程的Thread.jion(time),(有时间限制的)等待子线程执行完成返回。
                             线程调用LockSupport.parkNanos(Object blocker, long deadline).
                             线程调用LockSupport.partUntil(long deadline).
                             线程调用Thread.sleep(long millis).
    time_waiting-->runnable, 上述条件满足 或 等待超时

    runnable-->teminated, 线程自动终结:1,线程run方法执行结束退出; 2,线程run方法内部抛出异常退出
                             其他线程通过调用本线程的Tread.interrupt()方法,尝试强制中止本线程:【注:不要用Tread.stop()这个废弃方法,危害...】
                               1) 本线程处于waiting,timed_waiting状态时,其他线程调用本线程的interrupt(),会使本线程转为Runnable状态,同时本线程抛出InterruptedException.
                               2) 本线程处于Runnable状态,且阻塞在java.nio.channels.InterruptedChannel [可中断channel]时,其他线程调用本线程的interrupt(),会使本线程抛出ClosedByInterruptedException。
                               3) 本线程处于Runnable状态,且阻塞在java.nio.channels.Selector[可多路复用的异步IO机制]上时,本线程会(从Selector.select())立即返回。
                               4) 本线程处于Runnable状态,且没有阻塞在某个I/O操作上时,其他线程调用本线程的interrupt(),这只是将本线程的中断标志位置为True。
                                  本线程可以通过调用isInterrupted()来查看本线程的中断标志位是否被置为true,可以决定退出,也可以忽略它(全看代码逻辑)。

                             重要:本线程抛出InterruptedException后,会把本线程的中断标志位清空,可能已有的中断标志True就消失了,可能会引起本线程失去主动监测中断标志以退出的机会!
                                  所以,对本线程抛出的InterruptedException的异常try-catch后,再主动置标志位为True。Thead.currentTread().interrupt();
    2019-06-20
    6
  • 向往的生活
    当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用线程 A 的 interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发 InterruptedException 异常。此时如果线程A获取不到锁,岂不是会立马又变成BLOCKED 状态?

    作者回复: 我估计不会有中间的runnable,只是换个队列而已

    2019-03-19
    1
    5
  • Aven
    老师,您好,想问下,在讲到“java调用阻塞api的情况下,java程序仍然是Runnable状态”这里的时候,我不太理解,哪些api是属于阻塞api呢

    作者回复: 阻塞式的读文件,读网络数据都是

    2019-07-02
    4
  • Docker
    测试过了,确实一个线程获取不到锁,线程状态为blocked
    2019-03-20
    4
  • 星辰

    public class TestThread {

    public static void main(String[] args) throws InterruptedException {

    Worker t = new Worker();
    t.start();

    Thread.sleep(2000);

    System.out.println("-1-1-1-");
    t.interrupt();
    System.out.println("000000");
    Thread.sleep(2000);
    t.stop();
    System.out.println("000111");
    Thread.sleep(2000);
    t.join();
    System.out.println("111111");
    }

    }

    class Worker extends Thread {

    @Override
    public void run() {
    int i = 0;
    while (i<20) {
    if (Thread.currentThread().isInterrupted()) {
    break;
    }
    ++i;
    System.out.println(Thread.currentThread().getName() + "i: " + i);
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    }
    }

    ----------------------------------------------------------------------------
    忽然发现极客时间网页版的留言窗口好小啊,都看不到自己上面写的东西...
    ----------------------------------------------------------------------------

    1. 如果worker中没有sleep方法,则调用th.interrupt()方法会真正的中断th线程,并且不会抛出InterruptException 但是该演示代码不能体现锁的释放;
    2. 如果worer中有sleep方法,则调用th.interrupt()方法会抛 java.lang.InterruptException(), 是针对sleep方法抛出的
    同样的Object的wait() wait(带参) 也会抛出java.lang.InterruptException()而从当前的wait/blocked状态被中断(唤醒)
    那也就是说,throws InterruptedException 的方法 在线程被调用interrupt()方法后,会被从当前状态中断
    至于调用interrupy()方法后线程的状态属于哪种,取决于interrupt方法前的执行的方法使得当前线程处于哪种状态,
    老师的总结很到位,需要好好理解,感受~!
    3. 无论worder的run中有没有slee()方法,stop都会直接中断线程,当前演示代码也无法演示锁没有被释放
    4. join()总是在等待被调用的线程执行完毕
    5. while循环放在try里面, 在调用th.interrupt之后,可以有效捕获InterruptException 从而使th线程中断

    说的有点多了, 大家多多讨论~!~!~!

    作者回复: 好认真👍👍👍

    2019-03-20
    4
  • 海鸿
    如果线程处于阻塞状态(BLOCKED),此时调用线程的中断方法,线程会又如何反应?
    是否会像等待状态一样抛异常?
    还是会像运行状态一样被标记为已中断状态?
    还是不受到任何影响?
    麻烦老师解答一下😁

    作者回复: 阻塞态的线程不响应中断,并发包里的锁有方法能够响应中断

    2019-03-19
    4
  • xuery
    看了下留言,感觉自己的思考还可以深一点,应该接着问自己:如果这样不行,那应该采用什么样的写法呢,慢慢培养自己的思维习惯吧
    2019-04-22
    3
  • Dylan
    老师,Java调用阻塞API时,Java层面是runnable,那仍然占用CPU吗,此时此线程在操作系统中是什么状态呢?这个问题好几个人都在问,能详细解释下吗?

    作者回复: 不占cpu,操作系统里是阻塞状态。

    2019-03-24
    3
  • ren
    老师。那么jvm在进行gc的时候的停顿所有线程(stw) 这个期间 jvm中的线程应该属于生命周期的哪一个状态呢? 我看到有资料讲的是 jvm中的线程 会因为jvm设置的安全点和安全区域 执行test指令产生一个自陷异常信号 这个指令应该是汇编中的触发线程中断的 那么之后的恢复成运行状态也都是交给操作系统层面来实现的吗?

    作者回复: 线程调度是交给操作系统的,stw期间你看到的线程状态和stw之前应该是一样的,java里的线程状态是给你看的,没必要让你看到不该看的。但是stw期间操作系统层面的状态应该都是阻塞态,不允许调度。这个要看jvm的具体实现

    2019-03-26
    2
  • linqw
    老师,不知道能否在理论讲解清楚的同时也能补上对源码的分析,比如线程a的interrupt方法被其他线程调用,有两种形式检测,异常和使用isInterrupted检测,但是内部原理还是感觉不清楚不明白,根据异常它是如何中断的?还有java有阻塞和等待状态,但是没能理解java为什么要将其区分开来,比如阻塞是在获取不到锁阻塞,会在锁对象中的队列排队,wait等待状态,不是也会在调用的对象队列中排队么?不太清楚为什么要怎么做?

    作者回复: 我尽量不讲源码,讲源码的书有好多,感兴趣的可以去参考。也不回拿出汇编来讲解怎么实现的,网上也有很多。听完这个专栏去再去看代码,你会觉得很简单。

    区分这么多状态的原因我也没有深究,可能是历史原因,如果并发包里的锁也搞一状态,可能会更乱

    2019-03-24
    2
收起评论
73
返回
顶部