终止线程的设计模式

在一个线程 T1 中如何正确安全的终止线程 T2?

  • 错误思路1,使用stop,top 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁。
  • 错误思路2:使用 System.exit(int) 方法停止线程,这种做法会让整个程序都停止

    Two-phase Termination(两阶段终止)模式

    将终止过程分成两个阶段,其中第一个阶段主要是线程 T1 向线程 T2发送终止指令,而第二阶段则是线程 T2响应终止指令。
    Java 线程进入终止状态的前提是线程进入 RUNNABLE 状态,而利用java线程中断机制的interrupt() 方法,可以让线程从休眠状态转换到RUNNABLE 状态。RUNNABLE 状态转换到终止状态,优雅的方式是让 Java 线程自己执行完 run() 方法,所以一般我们采用的方法是设置一个标志位,然后线程会在合适的时机检查这个标志位,如果发现符合终止条件,则自动退出 run() 方法。 ```java public class MonitorProxy { boolean started = false; //采集线程 Thread rptThread;

    //启动采集功能 synchronized void start() {

    1. //不允许同时启动多个采集线程
    2. if (started) {
    3. return;
    4. }
    5. started = true;
    6. rptThread = new Thread(() -> {
    7. while (!Thread.currentThread().isInterrupted()) {
    8. //省略采集、回传实现
    9. report();
    10. //每隔两秒钟采集、回传一次数据
    11. try {
    12. Thread.sleep(2000);
    13. } catch (InterruptedException e) {
    14. //重新设置线程中断状态
    15. Thread.currentThread().interrupt();
    16. }
    17. }
    18. //执行到此处说明线程马上终止
    19. started = false;
    20. });
    21. rptThread.start();

    }

    private void report() {

    1. System.out.println("采集数据");

    }

    //终止采集功能 synchronized void stop() {

    1. rptThread.interrupt();

    }

  1. public static void main(String[] args) {
  2. MonitorProxy monitor = new MonitorProxy();
  3. monitor.start();
  4. try {
  5. Thread.sleep(5000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. monitor.stop();
  10. }

}

  1. <a name="M8MrO"></a>
  2. ## 避免共享的设计模式
  3. Immutability模式,Copy-on-Write模式,Thread-Specific Storage模式本质上都是为了避免共享
  4. <a name="iQw58"></a>
  5. ### **Immutability模式**
  6. 多个线程同时读写同一共享变量存在并发问题,读读是可以共享的,并发问题主要是同时出现读和写。解决并发问题,其实最简单的办法就是让共享变量只有读操作,而没有写操作。简单来讲,就是对象一旦被创建之后,不允许修改了,没有修改操作,也就是保持了不变性。
  7. <a name="Ki7YQ"></a>
  8. #### **如何实现**
  9. 将一个类所有的属性都设置成 final 的,并且只允许存在只读方法,那么这个类基本上就具备不可变性了。更严格的做法是这个类本身也是 final 的,也就是不允许继承。
  10. <a name="fvANw"></a>
  11. ### **Copy-on-Write模式**
  12. Copy-on-Write,也就是写时复制。就是对不可变对象的修改时,没有更改原来的内容,而是创建了一个新的内容。Copy-on-Write 缺点就是消耗内存,每次修改都需要复制一个新的对象出来,适合读多写少的场景。
  13. <a name="kOpkE"></a>
  14. ### **Thread-Specific Storage 模式**
  15. Thread-Specific Storage(线程本地存储) 模式是一种即使只有一个入口,也会在内部为每个线程分配特有的存储空间的模式。在 Java 中ThreadLocal 类实现了该模式。线程本地存储模式本质上是一种避免共享的方案,由于没有共享,所以自然也就没有并发问题。<br />SimpleDateFormat 不是线程安全的,但是可以用ThreadLocal来解决线程安全的问题
  16. ```java
  17. static class SafeDateFormat {
  18. //定义ThreadLocal变量
  19. static final ThreadLocal<DateFormat> tl=ThreadLocal.withInitial(
  20. ()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
  21. static DateFormat get(){
  22. return tl.get();
  23. }
  24. }