Java并发编程实战
王宝令
资深架构师
立即订阅
15034 人已学习
课程目录
已完结 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并发编程实战
登录|注册

35 | 两阶段终止模式:如何优雅地终止线程?

王宝令 2019-05-18
前面两篇文章我们讲述的内容,从纯技术的角度看,都是启动多线程去执行一个异步任务。既启动,那又该如何终止呢?今天咱们就从技术的角度聊聊如何优雅地终止线程,正所谓有始有终。
《09 | Java 线程(上):Java 线程的生命周期》中,我曾讲过:线程执行完或者出现异常就会进入终止状态。这样看,终止一个线程看上去很简单啊!一个线程执行完自己的任务,自己进入终止状态,这的确很简单。不过我们今天谈到的“优雅地终止线程”,不是自己终止自己,而是在一个线程 T1 中,终止线程 T2;这里所谓的“优雅”,指的是给 T2 一个机会料理后事,而不是被一剑封喉。
Java 语言的 Thread 类中曾经提供了一个 stop() 方法,用来终止线程,可是早已不建议使用了,原因是这个方法用的就是一剑封喉的做法,被终止的线程没有机会料理后事。
既然不建议使用 stop() 方法,那在 Java 领域,我们又该如何优雅地终止线程呢?

如何理解两阶段终止模式

前辈们经过认真对比分析,已经总结出了一套成熟的方案,叫做两阶段终止模式。顾名思义,就是将终止过程分成两个阶段,其中第一个阶段主要是线程 T1 向线程 T2发送终止指令,而第二阶段则是线程 T2响应终止指令
两阶段终止模式示意图
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(37)

  • 佑儿
    stop和start方法对于terminated访问由于syn关键字,线程安全,但是start中新起了一个线程rptthread,导致stop方法中对于terminated存在可见性问题,因此需要volatie,原子性问题对这个代码段没有影响,所以原子性问题无需关注。

    作者回复: 👍

    2019-05-20
    14
  • 孙志强
    有必要,变量被多个线程访问,需要保证可见性

    作者回复: 👍

    2019-05-18
    10
  • echo_陈
    我觉得,在本例子中。stop中,设置终止标识位对interupt是可见的。而interrupt对被中断线程检测到中断事件是可见的……根据传递性原则……我觉得本例子不需要volatile关键字。但平时开发中,一般会加上,主要是因为怕后续开发不注意这些事情导致修改破坏了规则,引起可见性问题产生bug,保险起见会加上volatile

    作者回复: 是的,线程不调用wait,sleep等方法,是无法响应中断的,这个时候基于interrupt的可见性就不成立了,所以工程上这类变量都需要加volatile

    2019-05-18
    2
    5
  • ZOU志伟
    shutdown()调用后,还要再调用awaitTermination方法等待一点时间,线程池里的线程才会终止。
    2019-05-18
    4
  • WL
    请问一下老师"JVM 的异常处理会清除线程的中断状态"指的是什么意思, 是指把线程的为true的中断状态改为false吗, JVM是在catch到Interrupt异常的时候重置线程中断状态的吗?

    作者回复: 是的

    2019-05-20
    3
  • 晓杰
    有必要,因为stop方法对isTerminated的修改需要被start方法读取到,保证共享变量的可见性
    2019-05-19
    3
  • null
    ```java
    // 因为留言超字数:1. 省略未修改的代码片段,2. println 是 System.out.println 的简写
    class Proxy {
    // 变量声明,(留言超字数,此处未做修改,省略)

    public static void main(String[] args) {
      Proxy proxy=new Proxy();
      for (int i=0; i<100; i++) {
        new Thread(() -> {
        proxy.start();
        proxy.stop();
        }, "外部线程_"+i)
        .start();
      }
    }

    // 启动采集功能
    synchronized void start() {
      // 不允许同时启动多个采集线程
      String outerName=Thread.currentThread().getName();
      println("["+outerName+"]线程是否启动?"+started);

      if (started) {
        println("["+outerName+"]线程 return");
        return;
      }
      started=true;
      terminated=false;

      rptThread=new Thread(() -> {
        while (!terminated) {
          // 每隔两秒钟采集、回传一次数据(留言超字数,此处未做修改,省略)
        }
        // 执行到此处说明线程马上终止
        started=false;
        println("["+outerName+",内部线程:"+Thread.currentThread().getName()+"] started=false 成功执行");
      });

      rptThread.start();
      println("["+outerName+"]线程执行完毕,内部子线程正在执行中...");
    }

    // 终止采集功能(留言超字数,此处未做修改,省略)
    }
    ```

    ```
    执行结果:
    [外部线程_77]线程是否启动?false
    [外部线程_77]线程执行完毕,内部子线程正在执行中...
    [外部线程_82]线程是否启动?true
    [外部线程_82]线程 return
    [外部线程_81]线程是否启动?false
    [外部线程_77,内部线程:Thread-72] started=false 成功执行
    [外部线程_81]线程执行完毕,内部子线程正在执行中...
    [外部线程_81,内部线程:Thread-73] started=false 成功执行
    [外部线程_84]线程是否启动?false
    [外部线程_84]线程执行完毕,内部子线程正在执行中...
    [外部线程_80]线程是否启动?true
    [外部线程_84,内部线程:Thread-74] started=false 成功执行
    [外部线程_80]线程执行完毕,内部子线程正在执行中...
    [外部线程_79]线程是否启动?true
    [外部线程_80,内部线程:Thread-75] started=false 成功执行
    ```

    解释说明:
    1. “[外部线程_81]线程是否启动?false” 先于 “[外部线程_77,内部线程:Thread-72] started=false 成功执行”:
    [外部线程_77,内部线程:Thread-72] 执行完 started=false,还没执行 System.out 输出语句,[外部线程_81] 就已经拿到 started=false 的结果了。

    2. “[外部线程_80]线程是否启动?true” 然后又 “[外部线程_80]线程执行完毕,内部子线程正在执行中...”:
    这时[外部线程_80]让出了 cpu,等到时间片后再次执行时并没有 return,而是成功执行了内部子线程。

    结论:started 在线程之间可以保证可见性的,但是具体原因,自己也没想明白。

    -----

    自己套用了下面的 Happens-Before 规则:
    0. Happens-Before 的传递性。
    1. 管程中锁的规则。
    2. 线程启动规则。
    3. 线程终止规则。
    4. 线程中断规则。
    好像也无法推导出:为何在内部线程 rptThread 修改的 started 变量,可以保证可见性。
    是根据什么规则,保证了 started 变量的可见性,老师可以帮忙分析一下么?期待您的回复,谢谢老师!!

    作者回复: println内部有锁,而且还有io操作,所以会让结果不准确

    2019-06-08
    2
    2
  • 遇见阳光
    按道理而言,synchronized保证原子性的同时,也能间接的保证可见性啊。感觉可以不加 volatile关键字

    作者回复: 问题是start方法里又启动了一个新的线程,synchronized管不到这个新的线程

    2019-05-18
    2
  • 虚竹
    王老师好,请教下:
    1.自定义标志位终止线程时,是不是这样可以这样写?
    whlie(!Thread.currentThread()isInterrupted() || !terminated){}
    2.线程池关闭时,完整的是这样吧?
    exec.shutdown();
    while(true){
      if(exec.isTerminated()){
        print("所有的任务都结束了~");
      }
      Thread.sleep(1000);
    }

    作者回复: 可以这样写

    2019-10-12
    1
  • 胡小禾
    第一段代码中的第九行:
          started = true;
    有必要存在吗?

    作者回复: 核心逻辑是异步执行的,所以还是有必要的

    2019-07-08
    1
  • 八百
    我记得在kafka0.8版本的时候,我设置中断来关闭消费者线程,结果不行,然后我把中断标志位,恢复了,就好了。。Ծ‸Ծ
    2019-05-26
    1
  • ban
    老师,思考题前的最后一个示例代码,为什么
    // 线程终止标志位
    volatile boolean terminated = false;
    boolean started = false;

    为什么started可以不加volatile,terminated却要加呢?

    作者回复: started的读写都在同步方法里面

    2019-05-24
    1
  • shangyu
    请问老师 用interrupt方式中断线程是设置标志位,在run方法中用循环检测该中断标志位,如果中断则不再循环。
    这里有个问题是必须执行完循环里的代码,重新检测循环条件才能中断,那有没有办法能在循环中中断,实现类似ctrl-C的功能,用Thread.stop吗?
    2019-05-23
    1
  • 佑儿
    两阶段终止模式:发送终止指令+响应终止指令。
    终止指令通常可以定义一个终止标识变量(注意并发问题,需要volatie保证可见性)。
    如果线程中调用了可中断方法(wait等),在发送终止指令的同时需要调用Thread.interrupt()。
    不建议使用线程自身的中断标识作为终止指令,因为项目中第三方的调用无法保证该标志位。
    2019-05-20
    1
  • potato00fa
    有必要加volatie,父线程调用stop改标志位通知子线程,标志位属于跨线程的共享变量
    2019-11-02
  • Geek_844248
    老师,可以举一个调用第三方类库,第三方类库捕获了异常,却没有正常处理的例子吗?
    2019-10-28
  • 远东通信-应用软件
    在本章节后面一个实例代码中while循环没有使用线程的中断标志位做判断,但是stop里面仍然去调用rptThread.interrupt()有必要吗?只是为了将采集线程从sleep状态唤醒吗?

    作者回复: 是的

    2019-10-12
  • 老师,想问一个问题如果interrupt()方法只是给线程打一个中断的标签,那么如果我线程本身没有显示的去做这个标的判断,线程还能被中断么,当然线程是runnable的,如果能中断又是谁去识别的呢?

    作者回复: 线程不能被中断,但是很多系统函数如sleep是响应中断的。极端地讲,纯CPU计算一定不会被中断

    2019-07-16
  • 美美
    我看结束线程的示例都有while方法,如果没有while,如果中断呢?同时也没有处于休眠状态的话,是不是只能等程序自然结束了,是不是我的问题有问题。。。

    作者回复: 没有while可以关键点上检查,也可以等待自然结束,看实际需求

    2019-07-15
  • 胡小禾
    class Proxy {
      // 线程终止标志位
      volatile boolean terminated = false;
      boolean started = false;
      // 采集线程
      Thread rptThread;
      // 启动采集功能
      synchronized void start(){
        // 不允许同时启动多个采集线程
        if (started) {
          return;
        }
        started = true;
        terminated = false;
        rptThread = new Thread(()->{
          while (!terminated){
            // 省略采集、回传实现
            report();
            // 每隔两秒钟采集、回传一次数据
            try {
              Thread.sleep(2000);
            } catch (InterruptedException e){
              // 重新设置线程中断状态
              Thread.currentThread().interrupt();
            }
          }
          // 执行到此处说明线程马上终止
          started = false;
        });
        rptThread.start();
      }
      // 终止采集功能
      synchronized void stop(){
        // 设置中断标志位
        terminated = true;
        // 中断线程 rptThread
        rptThread.interrupt();
      }
    }

    这里使用了 terminated 作为中断标志位了,
    是不是说 就不需要 rptThread.interrupt()
    或者 Thread.currentThread().interrupt()
    的中断标志位了?
    2019-07-09
收起评论
37
返回
顶部