终止模式
两阶段终止模式(Two Phase Termination)
错误思路
1、使用线程对象的 stop() 方法停止线程
stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁
2、使用 System.exit(int) 方法停止线程
目的仅是停止一个线程,但这种做法会让整个程序都停止
两阶段终止模式–interrupt
@Slf4j(topic = "c.tpt")
public class TwoPhaseTermination {
private Thread monitor;
public void start(){
monitor = new Thread("t1"){
@Override
public void run() {
while (true){
if(Thread.currentThread().isInterrupted()){
log.debug("处理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("检测记录");
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
};
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
两阶段终止模式—volatile
class TwoPhaseTermination {
// 监控线程
private Thread monitorThread;
// 停止标记
private volatile boolean stop = false;
// 启动监控线程
public void start() {
monitorThread = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
// 是否被打断
if (stop) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
}, "monitor");
monitorThread.start();
}
// 停止监控线程
public void stop() {
stop = true;
monitorThread.interrupt();
}
}
同步模式
保护性暂停
保护性暂停即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
要点
1、有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject
2、如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
3、JDK 中,join 的实现
、Future的实现
,采用的就是此模式
4、使用wait—notifyall
public class GuardedObject {
private Object response;
private final Object obj = new Object();
public Object get(){
synchronized (obj){
while (response == null){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return response;
}
public void setResponse(Object response){
synchronized (obj){
this.response = response;
obj.notifyAll();
}
}
}
带超时版 GuardedObject
public static void main(String[] args) {
GuardedObject object = new GuardedObject();
new Thread(){
@Override
public void run() {
object.get(1000);
}
}.start();
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1020);
} catch (InterruptedException e) {
e.printStackTrace();
}
object.setResponse(10000);
}
}.start();
}
private Object response;
private final Object obj = new Object();
mill 表示要等待多久 2000
public Object get(long mill){
synchronized (obj){
//记录最初时间 15:00:00
long begin = System.currentTimeMillis();
//已经经历的时间
long timePassed = 0;
while (response == null){
// 这一轮循环应该等待的时间
long waitTime = mill - timePassed;
log.info("waitTime:{}",waitTime);
// 经历的时间超过了最大等待时间时,退出循环
if(waitTime <= 0){
log.info("break");
break;
}
try {
obj.wait(waitTime); // 虚假唤醒 15:00:01
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
timePassed = System.currentTimeMillis() - begin;
log.info("timePassed:{},response:{}",timePassed,response);
}
}
return response;
}
public void setResponse(Object response){
synchronized (obj){
this.response = response;
log.info("notify...");
obj.notifyAll();
}
}
}
多任务版 GuardedObject
结果产生者和结果消费者是一一对应的关系。
新增id用来标识Guarded Object
public class GuardedObject {
//标识Guarded Object
private int id;
public int getId() {
return id;
}
public GuardedObject(int id) {
this.id = id;
}
private Object response;
public Object get(long mill){
synchronized (this){
//记录最初时间
long begin = System.currentTimeMillis();
//已经经历的时间
long timePassed = 0;
while (response == null){
long waitTime = mill - timePassed;
if(waitTime <= 0){
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
timePassed = System.currentTimeMillis() - begin;
}
}
return response;
}
public void setResponse(Object response){
synchronized (this){
this.response = response;
this.notifyAll();
}
}
}
中间解耦类
ublic class MailBoxes {
private static Map<Integer,GuardedObject> boxes = new ConcurrentHashMap<>();
//产生唯一id
private static int id;
public static synchronized int generateId(){
return id++;
}
//送信人获取邮箱--GuardedObject
public static GuardedObject getGuarderObject(int id){
//根据唯一id获取邮箱并删除
return boxes.remove(id);
}
//收信人创建邮箱--GuardedObject
public static GuardedObject createGuarderObject(){
GuardedObject go = new GuardedObject(generateId());
boxes.put(go.getId(), go);
return go;
}
//获取所有id
public static Set<Integer> getIds(){
return boxes.keySet();
}
}
业务代码
@Slf4j
public class PostMan extends Thread{
private int id;
private String mail;
public PostMan(int id,String mail){
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObject guardedObject = MailBoxes.getGuarderObject(id);
log.info("送信id:{},内容:{}",id,mail);
guardedObject.setResponse(mail);
}
}
@Slf4j
class People extends Thread{
@Override
public void run() {
GuardedObject go = MailBoxes.createGuarderObject();
log.info("开始收信 id:{}",go.getId());
Object mail = go.get(5000);
log.info("收到的信 id:{}, mail:{}" ,go.getId(),mail);
}
}
犹豫模式(blaking)
Balking (犹豫)模式用在一个线程发现另一个线程或本线程已经做了某一件相同的事,那么本线程就无需再做了,直接结束返回
保证线程只启动一次,实现线程的单例
顺序控制
固定运行顺序(wait-notify版)
@Slf4j(topic = "c.Test25")
public class Test25 {
static final Object lock = new Object();
// 表示 t2 是否运行过
static boolean t2runned = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
while (!t2runned) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock) {
log.debug("2");
t2runned = true;
lock.notify();
}
}, "t2");
t1.start();
t2.start();
}
}
固定运行顺序(park-unpark版)
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();
}
}
交替输出(wait-notify版)
public class Test27 {
public static void main(String[] args) {
WaitNotify wn = new WaitNotify(1, 5);
new Thread(() -> {
wn.print("a", 1, 2);
}).start();
new Thread(() -> {
wn.print("b", 2, 3);
}).start();
new Thread(() -> {
wn.print("c", 3, 1);
}).start();
}
}
/*
输出内容 等待标记 下一个标记
a 1 2
b 2 3
c 3 1
*/
class WaitNotify {
// 打印 a 1 2
public void print(String str, int waitFlag, int nextFlag) {
for (int i = 0; i < loopNumber; i++) {
synchronized (this) {
while(flag != waitFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flag = nextFlag;
this.notifyAll();
}
}
}
// 等待标记
private int flag; // 2
// 循环次数
private int loopNumber;
public WaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
}
交替输出(await-singnal版)
public class Test30 {
public static void main(String[] args) throws InterruptedException {
AwaitSignal awaitSignal = new AwaitSignal(5);
Condition a = awaitSignal.newCondition();
Condition b = awaitSignal.newCondition();
Condition c = awaitSignal.newCondition();
new Thread(() -> {
awaitSignal.print("a", a, b);
}).start();
new Thread(() -> {
awaitSignal.print("b", b, c);
}).start();
new Thread(() -> {
awaitSignal.print("c", c, a);
}).start();
Thread.sleep(1000);
awaitSignal.lock();
try {
System.out.println("开始...");
a.signal();
} finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock{
private int loopNumber;
public AwaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}
// 参数1 打印内容, 参数2 进入哪一间休息室, 参数3 下一间休息室
public void print(String str, Condition current, Condition next) {
for (int i = 0; i < loopNumber; i++) {
lock();
try {
current.await();
System.out.print(str);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
交替输出(park-unpark版)
public class Test31 {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) {
ParkUnpark pu = new ParkUnpark(5);
t1 = new Thread(() -> {
pu.print("a", t2);
});
t2 = new Thread(() -> {
pu.print("b", t3);
});
t3 = new Thread(() -> {
pu.print("c", t1);
});
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
}
class ParkUnpark {
public void print(String str, Thread next) {
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(next);
}
}
private int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
}
异步模式
生产者消费者模式
1、与前面的保护性暂停中的 GuardObject 不同,不需要产生结果和消费结果的线程一一对应
2、消费队列可以用来平衡生产和消费的线程资源
3、生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
4、消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
5、JDK 中各种阻塞队列,采用的就是这种模式
消息实体类
public class Message {
private int id;
private Object message;
public Message(int id, Object message) {
this.id = id;
this.message = message;
}
public int getId() {
return id;
}
public Object getMessage() {
return message;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", message=" + message +
'}';
}
}
消息队列
//消息队列,java线程之间通信
public class MessageQueue {
//消息队列集合
private LinkedList<Message> list = new LinkedList<>();
//队列容量
private int capcity;
public int getCapcity() {
return capcity;
}
public MessageQueue(int capcity) {
this.capcity = capcity;
}
//获取消息
public Message take(){
//检查队列是否为空
synchronized (list){
while (list.isEmpty()){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = list.removeFirst();
list.notify();
return message;
}
}
//存入消息
public void put(Message message){
synchronized (list){
//检查队列是否已满
while (list.size() == capcity){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(message);
list.notify();
}
}
}
工作线程
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率。
饥饿
固定大小线程池会有饥饿现象。这是由于线程池的数量不足以及分工不明确导致导致的。
比如,t1和t2是线程池中的2个线程,他们都需要处理2个任务A,B任务,这是2个阶段任务,只有A执行完毕,B任务才能开始执行。
如果t1执行A,t2接着执行B,这是没有问题的。
但是如果同时来了2个用户,t1执行了第一个用户的A任务,t2执行了第2个用户的A任务,这就会造成B任务没有人执行,也就是所谓的饥饿现象。
解决方法:
不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率
享元模式
Flyweight pattern.享元模式
一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象
使用场景: 当需要重用数量有限的同一类对象时
体现
1、在JDK中 Boolean,Byte,Short,Integer,Long,Character 等包装类提供了 valueOf 方法,例如Long 的valueOf 会缓存 -128~127 之间的 Long 对象,在这个范围之间会重用对象,大于这个范围,才会新建 Long对象。
2、String串池
3、BigDecimal BigInteger