一、保护性暂停
1.1 定义
即Guarded Suspension,用在一个线程等待另一 个线程的执行结果
要点
● 有一个结果需要从一个线程传递到另一 个线程,让他们关联同一一个GuardedObject
● 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(生产者/消费者)
● JDK中,join的实现、Future的实现,采用的就是此模式
● 因为要等待另一方的结果, 因此归类到同步模式
1.2 实现
GuardedObject(保护对象),其response属性用来保存最终的结果(t1使用结果,t2产生结果),初始值为null(wait-notify在GuardedObject上等待结果)
模拟应用场景:线程1需要等待线程2产生的结果,线程2进行一个下载任务
import cn.itcast.pattern.Downloader;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.List;
@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) throws InterruptedException {
GuardedObject guardedObject = new GuardedObject();
new Thread(() -> {
// 等待结果
log.debug("等待结果");
List<String> list = guardedObject.get();
log.debug("结果的大小:{}", list.size());
}, "t1").start();
new Thread(() -> {
log.debug("执行下载");
try {
List<String> list = Downloader.download();
// 将下载结果传给线程1
guardedObject.complete(list);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
class uardedObject {
// 结果
private Object response;
// 获取结果的方法
public Object get() {
synchronized (this) {
// 还没有结果
while (response == null) {
// 调用wait等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
// 产生结果
public void complete(Object response) {
synchronized (this) {
// 给结果成员变量赋值
this.response = response;
// 通知等待线程
this.notifyAll();
}
}
}
运行结果:
1.3 保护性暂停扩展—增加超时
二、 两阶段终止-interrupt
Two Phase Termination
在一个线程T1中如何“优雅”终止线程T2?这里的【优雅】指的是给T2一个料理后事的机会。
错误思路
● 使用线程对象的stop()方法停止线程(强制杀死)
—— stop()方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
● 使用System.exit(int)方法停止线程
—— 目的仅是停止一个线程,但这种做法会让整个程序都停止
2.1 两阶段终止-interrupt分析
有如下场景,做一个系统的健康状态监控(记录电脑CPU的使用率、内存的使用率)实现定时监控。实现这样一个场景,可用一个后台的监控线程不断记录。
代码实现:
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt=new TwoPhaseTermination();
// 启动监控线程(每隔1秒执行监控记录)
tpt.start();
// 模拟非正常打断,主线程经过3.5后,被interrupt()===>优雅打断
Thread.sleep(3500);
tpt.stop();
}
}
// 监控类代码
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{
// 创建监控线程
private Thread monitor;
// 启动监控线程
public void start(){
// 创建线程对象
monitor=new Thread(()->{
// 不断被执行监控
while (true){
// 获取当前线程对象,判断是否被打断
Thread current = Thread.currentThread();
if(current.isInterrupted()){
// 若被打断
log.debug("料理后事");
break;
}
// 若未被打断(每隔2s执行睡眠,进行监控操作)
try {
Thread.sleep(1000); // 情况1===>非正常打断(睡眠过程中)
log.debug("执行监控记录"); // 情况2===>正常打断
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标记(sleep()被打断后会清除打断标记)
current.interrupt();
}
}
});
monitor.start();
}
// 停止监控线程
public void stop(){
// "优雅"打断
monitor.interrupt();
}
}
运行结果:
分析:监控线程每隔1s监控系统,主线程处于休眠状态,3.5秒后休眠状态被打断
*****interrupted()与isInterrupted()均为判断当前线程是否被打断,表面上看起来类似。但却有着很大的区别,调用isInterrupted()不会清除打断标记,而调用interrupted()判断完后会将打断标记清除
三、固定运行顺序
同步模式之顺序控制
比如,先打印2后打印1(如果不加控制两个线程被CPU调度的时间不受控制)
3.1 wait notify版
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test25")
public class Test25 {
// 锁对象
static final Object lock = new Object();
// 表示 t2 是否运行过
static boolean t2runned = false;
public static void main(String[] args) {
// 打印1的线程(线程1期待线程2打印过后将标记置为真后再打印)
Thread t1 = new Thread(() -> {
synchronized (lock) {
while (!t2runned) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}
}, "t1");
// 打印2的线程
Thread t2 = new Thread(() -> {
synchronized (lock) {
log.debug("2");
t2runned = true;
lock.notify();
}
}, "t2");
t1.start();
t2.start();
}
}
运行结果:
3.2 park unpack版
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.Test26")
public class Test26 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
log.debug("1");
}, "t1");
t1.start();
new Thread(() -> {
log.debug("2");
LockSupport.unpark(t1);
},"t2").start();
}
}
运行结果:
3.3 ReentrantLock——await&signal
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test24")
public class Test24 {
static final Object room = new Object();
static boolean flag = false;
static ReentrantLock ROOM = new ReentrantLock();
// 创建一个新的条件变量(休息室)
static Condition waitSet = ROOM.newCondition();
public static void main(String[] args) throws InterruptedException {
// 打印“1”的线程
Thread t1 = new Thread(() -> {
ROOM.lock();
try {
log.debug("2是否打印完毕[{}]", flag);
while (!flag) {
log.debug("未打印2,先歇会!");
try {
waitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
} finally {
// 解锁
ROOM.unlock();
}
});
// 打印“2”的线程
Thread t2=new Thread(()->{
ROOM.lock();
try {
log.debug("2");
flag=true;
// 唤醒线程
waitSet.signal();
}finally {
ROOM.unlock();
}
});
t1.start();
t2.start();
}
}
四、交替输出
线程1输出a 5次,线程2输出b 5次,线程3输出c 5次。现在要求输出abcabcabcabcabc怎么实现
4.1 wait notify版
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
Wait_notify wait_notify = new Wait_notify(1,5);
// 线程t1打印a
new Thread(() -> {
wait_notify.print("a",1,2);
}, "t1").start();
// 线程t1打印b
new Thread(() -> {
wait_notify.print("b",2,3);
}, "t2").start();
// 线程t3打印c
new Thread(() -> {
wait_notify.print("c",3,1);
}, "t3").start();
}
}
/*输出内容 等待标记 下一个标记
a 1 2
b 2 3
c 3 1*/
class Wait_notify {
// 等待标记【存在3个线程,因此用blooen变量不太合适(blooen变量的状态只有两个)】
private int flag; // 1: t1 2: t2 3: t3
// 循环次数
private int loopnumber;
public Wait_notify(int flag, int loopnumber) {
this.flag = flag;
this.loopnumber = loopnumber;
}
// 打印方法(打印内容,打印标记)
public void print(String s, int wait, int nextFlag) {
for (int i = 0; i < loopnumber; i++) {
synchronized (this) {
while (flag != wait) {
// 进入等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(s);
flag = nextFlag;
// 唤醒其他等待的线程
this.notifyAll();
}
}
}
}
运行结果:
4.2 ReentrantLock——await&signal
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) throws InterruptedException {
Awaitsynch awaitsynch = new Awaitsynch(5);
// 线程1的休息室
Condition a = awaitsynch.newCondition();
// 线程2的休息室
Condition b = awaitsynch.newCondition();
// 线程3的休息室
Condition c = awaitsynch.newCondition();
new Thread(() -> {
awaitsynch.print("a", a, b);
}).start();
new Thread(() -> {
awaitsynch.print("b", b, c);
}).start();
new Thread(() -> {
awaitsynch.print("c", c, a);
}).start();
// 三个线程刚开始都会进入各自休息室进行休息(利用主线程先将a休息室中的线程唤醒)
Thread.sleep(1000);
awaitsynch.lock();
try {
System.out.println("开始......");
// 唤醒a休息室中的线程
a.signal();
} finally {
awaitsynch.unlock();
}
}
}
class Awaitsynch extends ReentrantLock {
// 循环次数
private int loopnumber;
public Awaitsynch(int loopnumber) {
this.loopnumber = loopnumber;
}
// (打印内容,进入的休息室,下一个休息室)
public void print(String s, Condition con, Condition next) {
for (int i = 0; i < loopnumber; i++) {
// 给当前线程加锁
lock();
try {
con.await();
System.out.print(s);
// 唤醒下一个休息室中的线程
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
运行结果:
4.3 park unpack版
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.Test24")
public class Test24 {
static Thread a;
static Thread b;
static Thread c;
public static void main(String[] args) throws InterruptedException {
ParkUnpark parkUnpark = new ParkUnpark(5);
a=new Thread(() -> {
parkUnpark.print("a", b);
});
b=new Thread(() -> {
parkUnpark.print("b", c);
});
c=new Thread(() -> {
parkUnpark.print("c", a);
});
a.start();
b.start();
c.start();
// 主线程唤醒当前暂停的线程
LockSupport.unpark(a);
}
}
class parkUpark {
// 循坏次数
private int loopNumber;
public parkUpark(int loopNumber) {
this.loopNumber = loopNumber;
}
// print(打印的内容,要唤醒的线程)
public void print(String s, Thread next) {
for (int i = 0; i < loopNumber; i++) {
// 暂停当前线程(阻塞)
LockSupport.park();
System.out.println(s);
// 唤醒下一个线程
LockSupport.unpark(next);
}
}
}