终止线程的设计模式
在一个线程 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() {
//不允许同时启动多个采集线程if (started) {return;}started = true;rptThread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {//省略采集、回传实现report();//每隔两秒钟采集、回传一次数据try {Thread.sleep(2000);} catch (InterruptedException e) {//重新设置线程中断状态Thread.currentThread().interrupt();}}//执行到此处说明线程马上终止started = false;});rptThread.start();
}
private void report() {
System.out.println("采集数据");
}
//终止采集功能 synchronized void stop() {
rptThread.interrupt();
}
public static void main(String[] args) {MonitorProxy monitor = new MonitorProxy();monitor.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}monitor.stop();}
}
<a name="M8MrO"></a>## 避免共享的设计模式Immutability模式,Copy-on-Write模式,Thread-Specific Storage模式本质上都是为了避免共享<a name="iQw58"></a>### **Immutability模式**多个线程同时读写同一共享变量存在并发问题,读读是可以共享的,并发问题主要是同时出现读和写。解决并发问题,其实最简单的办法就是让共享变量只有读操作,而没有写操作。简单来讲,就是对象一旦被创建之后,不允许修改了,没有修改操作,也就是保持了不变性。<a name="Ki7YQ"></a>#### **如何实现**将一个类所有的属性都设置成 final 的,并且只允许存在只读方法,那么这个类基本上就具备不可变性了。更严格的做法是这个类本身也是 final 的,也就是不允许继承。<a name="fvANw"></a>### **Copy-on-Write模式**Copy-on-Write,也就是写时复制。就是对不可变对象的修改时,没有更改原来的内容,而是创建了一个新的内容。Copy-on-Write 缺点就是消耗内存,每次修改都需要复制一个新的对象出来,适合读多写少的场景。<a name="kOpkE"></a>### **Thread-Specific Storage 模式**Thread-Specific Storage(线程本地存储) 模式是一种即使只有一个入口,也会在内部为每个线程分配特有的存储空间的模式。在 Java 中ThreadLocal 类实现了该模式。线程本地存储模式本质上是一种避免共享的方案,由于没有共享,所以自然也就没有并发问题。<br />SimpleDateFormat 不是线程安全的,但是可以用ThreadLocal来解决线程安全的问题```javastatic class SafeDateFormat {//定义ThreadLocal变量static final ThreadLocal<DateFormat> tl=ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));static DateFormat get(){return tl.get();}}
