* 同步模式之两阶段终止(Two Phase Termination)
在一个线程 t1 如何优雅结束线程 t2 ? 优雅的意思是:给线程t2 一个料理后事的机会
- 错误思路
- 使用线程对象的stop 方法停止线程
- stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当他被杀死后,就再也没有机会释放锁,其他线程将永远无法获取锁
- 使用System.exit(int)方法停止线程
- 目的仅是停止一个线程,但这种做法会让整个程序都停止
例如:要做一个系统的健康状况监控,没个两秒记录一次
public class InterruptModeTest {
public static void main(String[] args) {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
twoPhaseTermination.stop();
}
}
class TwoPhaseTermination{
private Thread monitor;
//启动监控线程
public void start(){
monitor = new Thread(()->{
while (true){
if (Thread.currentThread().isInterrupted()){
System.out.println("料理后事");
break;
}
try {
Thread.sleep(1000); //情况一:睡眠过程中被打断
System.out.println("执行监控..."); //情况二
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt(); //重新设置打断标记
}
}
});
monitor.start();
}
//终止线程
public void stop(){
monitor.interrupt();
}
}
执行结果:
执行监控...
执行监控...
执行监控...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lily.base.TwoPhaseTermination.lambda0(InterruptModeTest.java:32)
at java.lang.Thread.run(Thread.java:748)
料理后事
细节一:sleep 时打断
细节二:执行监控记录时打断
同步模式之保护性暂停
即Guarded Suspension, 用在一个线程等待另一个线程的执行结果。
要点
有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject
如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
JDK中,join 的实现,Future 的实现,采用的就是此模式
-
因为要等待另一方的结果,因此归类到同步模式
代码实现
保护性暂停-扩展-增加超时
join方法
看一下join 的源码
保护性暂停-扩展-解耦等待和生产-分析
图中Futures就好比居民楼一层的信箱(每个信箱有房间编号),左侧的t0,t2,t4就好比等待邮件的居民,右侧t1,t3,t5就好比邮递员。
如果需要在多个类之间使用GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。
异步模式之工作线程
- 定义
让有限的工作线程来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果每位客人都配备一名专属的服务员,那么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message)
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率
例如,如果一个餐馆的工人既要招呼客人,又要到后厨做菜显然效率不高,分成服务员(线程池A),厨师(线程池B)更为合理,当然你能想到更细致的分工。
2.饥饿
固定大小线程池会有饥饿现象
- 两个工人是同一个线程池的两个线程
- 他们要做的事是:为客人点餐和到后厨做菜,这时两个阶段的工作。
- 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
- 后厨做菜:没啥说的,做就是了
- 比如工人A处理了点餐任务,接下来它要等着工人B把菜做好,然后上菜,他两也配合的蛮好的
- 但现在同时来了两个客人,这个时候工人A 和 工人B 都去处理点餐了,这时没人做菜了,死锁
public class TestDeadLock {
static final List<String> MENU = Arrays.asList("地三鲜","宫保鸡丁","辣子鸡丁","烤翅");
static Random RANDOM = new Random();
static String cooking(){
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) throws InterruptedException{
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(()->{
System.out.println("处理点餐...");
Future<String> f = pool.submit(()->{
System.out.println("做菜");
return cooking();
});
try {
System.out.println("上菜 " + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
pool.execute(()->{
System.out.println("处理点餐...");
Future<String> f = pool.submit(()->{
System.out.println("做菜");
return cooking();
});
try {
System.out.println("上菜 " + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
执行结果:
处理点餐...
处理点餐...
饥饿解决
不同的任务类型使用不同的线程池
public class TestDeadLock {
static final List<String> MENU = Arrays.asList("地三鲜","宫保鸡丁","辣子鸡丁","烤翅");
static Random RANDOM = new Random();
static String cooking(){
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) throws InterruptedException{
ExecutorService orderPool = Executors.newFixedThreadPool(2);
ExecutorService cookPool = Executors.newFixedThreadPool(2);
orderPool.execute(()->{
System.out.println(Thread.currentThread().getName() + " 处理点餐...");
Future<String> f = cookPool.submit(()->{
System.out.println(Thread.currentThread().getName() + " 做菜");
return cooking();
});
try {
System.out.println(Thread.currentThread().getName() + " 上菜 " + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
orderPool.execute(()->{
System.out.println(Thread.currentThread().getName() + " 处理点餐...");
Future<String> f = cookPool.submit(()->{
System.out.println(Thread.currentThread().getName() + " 做菜");
return cooking();
});
try {
System.out.println(Thread.currentThread().getName() +" 上菜 " + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
执行结果:
pool-1-thread-1 处理点餐...
pool-1-thread-2 处理点餐...
pool-2-thread-1 做菜
pool-1-thread-2 上菜 烤翅
pool-2-thread-2 做菜
pool-1-thread-1 上菜 辣子鸡丁