在多线程编程中,其实就是分工、协作、互斥。在很多场景中,比如A执行的过程中需要同步等待另外一个线程处理的结果,这种方式下,就是一种等待唤醒的机制。本篇我们来讲述等待唤醒机制的三种实现,以及对应的应用场景。
Guarded Suspension 模式
Guarded Suspension 翻译过来就是保护性暂停。其实就是一个线程需要等待获取另外一个线程执行的结果,先把当前线程挂起,另外一个线程执行完毕之后,通知自己,结束阻塞状态,继续执行。
等待唤醒的规范实现如下:
- sychronized+wait/notify/notifyAll
- reentrantLock+Condition(await/singal/singalAll)
- cas+park/unpark
其实底层以来的是pthread,pthread_mutex_lock/unlock pthread_cond_wait/singal。这里就不介绍了,感兴趣的朋友可以自行查阅。
解决线程之间的协作不可避免会用到阻塞唤醒机制
实际编码
syn
java
package com.jia.suspension;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author qxlx
* @date 2023/12/30 3:13 PM
*/
public class SynTest {
private Object obj;
public Object read() {
synchronized (this) {
while (Objects.isNull(obj)) {
try {
System.out.println(Thread.currentThread().getName()+ " wait-before");
this.wait();
System.out.println(Thread.currentThread().getName()+ " wait-after");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return obj;
}
}
public void write() {
System.out.println(Thread.currentThread().getName()+ " write");
synchronized (this) {
obj = new Object();
System.out.println(Thread.currentThread().getName()+ " notifyAll-before");
this.notifyAll();
System.out.println(Thread.currentThread().getName()+ " notifyAll-after");
}
}
public static void main(String[] args) throws InterruptedException {
SynTest synTest = new SynTest();
new Thread(()-> {
synTest.read();
}).start();
new Thread(()-> {
synTest.write();
}).start();
TimeUnit.SECONDS.sleep(2);
}
}
切记 不能在main线程中启动,需要单独创建两个线程去执行,否则main线程阻塞的话,程序就会阻塞不会执行下去。
conditon
java
package com.jia.suspension;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author qxlx
* @date 2023/12/30 3:31 PM
*/
public class ConditionTest {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private Object obj;
public Object read () {
try {
lock.lock();
while (obj == null) {
System.out.println("getLock");
condition.await();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return obj;
}
public void write() {
try {
lock.lock();
obj = new Object();
condition.signalAll();
System.out.println("唤醒");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionTest test = new ConditionTest();
new Thread(()-> {
test.read();
}).start();
new Thread(()-> {
test.write();
}).start();
TimeUnit.SECONDS.sleep(1);
}
}
LockSouport
java
package com.jia.suspension;
import java.util.concurrent.locks.LockSupport;
/**
* @author qxlx
* @date 2023/12/30 3:38 PM
*/
public class LockSupportTest {
private Object obj;
public Object read() {
while (obj == null) {
System.out.println("read-线程等待");
LockSupport.park();
System.out.println("read-线程唤醒");
}
return obj;
}
public void write(Thread thread) {
obj = new Object();
LockSupport.unpark(thread);
System.out.println("唤醒线程");
}
public static void main(String[] args) {
LockSupportTest lockSupportTest = new LockSupportTest();
Thread thread = new Thread(() -> {
lockSupportTest.read();
});
thread.start();
Thread thread2 = new Thread(() -> {
lockSupportTest.write(thread);
});
thread2.start();
}
}
好了以上就是三种唤醒阻塞的方式。
应用场景
- 多线程环境下多个线程访问相同实例资源,从实例资源中获得资源并处理;
- 实例资源需要管理自身拥有的资源,并对请求线程的请求作出允许与否的判断
在实际的开发中,我们对外提供一个API数据查询的接口,但是需要以来下游系统进行组合数据,将结果写入MQ,下游服务处理完毕后,然后另外一个线程进行获取数据处理。
从图中可以看从处理web请求的是蓝色的线程,而从Topic获取数据的线程是红色线程,也就是蓝色线程异步写入Topic数据后,会阻塞,等待红色线程获取结果后,然后在返回结果。