并发学习记录04
wait¬ify
wait作用
有时候线程运行时需要用到其他资源如IO,就需要进入IO等待状态,如果这个线程一直占用锁等待的话,其他线程也不能用CPU,效率非常低,所以需要在得到这个锁之后主要调用wait方法,进入waitSet等待,等其他资源满足了调用notify方法唤醒这个线程,让这个线程重新进入竞争锁的队列
wait¬ify原理
当前线程发现条件不满足,一般是缺少了某种资源,即可进入WaitSet变为WAITING状态,类似于操作系统中的阻塞状态
BLOCKED和WAITING线程都处于阻塞状态,不占用CPU时间片;其实是一个对应就绪状态,一个对应阻塞状态
BLOCKED 线程会在 Owner 线程释放锁时唤醒
WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
API介绍
obj.wait() 让进入 object 监视器的线程到 waitSet 等待
obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于** Object 对象**的方法。必须获得此对象的锁,才能调用这几个方法
@Slf4j(topic = "ch.TestWaitNotify")
public class TestWaitNotify {
final static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (object) {
log.debug("t1开始");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其他代码");
}
}, "t1").start();
new Thread(() -> {
synchronized (object) {
log.debug("t1开始");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其他代码");
}
}, "t2").start();
Thread.sleep(3000);
synchronized (object) {
// object.notify();
object.notifyAll();
}
}
}
wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止
wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify
wait notify的正确用法
sleep(long n) 和 wait(long n) 的区别
*sleep是线程方法,wait是object的方法
*sleep不需要强制配合synchronized使用,但是wait需要和synchronized一起使用
*sleep在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁
*状态都是TIMED_WAITING
代码模板如下:
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}
设计模式之保护性暂停
定义
用一个线程去等待另一个线程的执行结果
图示
注意
*如果有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject
*如果有结果不断的从一个线程到另一个线程,那么可以使用消息队列来解决同步问题
*JDK中,join的实现,Future的实现,采用的就是此模式
*因为一方要等待另一方的结果,因此归类到同步模式
代码实现
@Slf4j(topic = "ch.GuardedSuspension01")
public class GuardedSuspension01 {
private Object response;
private final Object lock = new Object();
public Object get() {
synchronized (lock) {
while (response == null) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return response;
}
public void complete(Object response) {
synchronized (lock) {
this.response = response;
lock.notify();
}
}
public static void main(String[] args) {
GuardedSuspension01 gs = new GuardedSuspension01();
new Thread(() -> {
try {
log.debug("下载开始");
// List<String> response = Downloader.download();
// 模拟下载
Thread.sleep(20000);
List<String> response = Arrays.asList("1", "2", "3");
log.debug("下载完成");
gs.complete(response);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
log.debug("主线程");
Object response = gs.get();
log.debug("得到结果:{}", response);
}
}
带超时时间版本的GuardedObject
@Slf4j(topic = "ch.GuardedSuspension02")
public class GuardedSuspension02 {
private Object response;
private final Object lock = new Object();
public Object get(long maxTime) {
synchronized (lock) {
long beginTime = System.currentTimeMillis();
long timePassed = 0;
while (response == null) {
log.debug("结果暂时为空");
long leftWaitTime = maxTime - timePassed;
log.debug("余下的等待时间是:{}", leftWaitTime);
if (leftWaitTime <= 0) {
log.debug("等了{}时间,不等了", timePassed);
break;
}
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
timePassed = System.currentTimeMillis() - beginTime;
log.debug("被唤醒了,结果是{},等待时间是{}", response, timePassed);
}
log.debug("返回结果是{}", response);
return response;
}
}
public void complete(Object response) {
synchronized (lock) {
this.response = response;
lock.notifyAll();
}
}
public static void main(String[] args) {
GuardedSuspension02 gs2 = new GuardedSuspension02();
new Thread(() -> {
try {
Thread.sleep(1000);
gs2.complete(null);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
gs2.complete(Arrays.asList("a", "b", "c"));
}, "t1").start();
Object response = gs2.get(1500);
if (response != null) {
log.debug("response结果是:{}", response);
} else {
log.debug("没得到response");
}
}
}
join原理
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
多任务版本的GuardedObject
图中futures就好比居民楼的信箱,每个信箱都有房间的编号,左侧的t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比是邮递员
如果需要在多个类之间使用GuardedObject对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能解耦结果等待者和结果生产者,还能同时支持多个任务的管理。
异步模式之生产者消费者
定义
*与前面的保护性暂停中的GuardObject不同,不需要产生结果和消费结果的线程一一对应
*消费队列可以用来平衡生产和消费的线程资源
*生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
*消费队列是有容量限制的,满了就不会再加入数据,空了就不会再消耗数据
*JDK中各种阻塞队列,采用的就是这种模式
实现
@Slf4j(topic = "ch.Test")
public class Test {
public static void main(String[] args) {
MessageQueue messageQueue = new MessageQueue(2);
for (int i = 0; i < 4; i++) {
int id = i;
new Thread(() -> {
log.debug("download...");
try {
List<String> result = Downloader.download();
log.debug("放入消息队列{}", id);
messageQueue.put(new Message(id, result));
} catch (IOException e) {
e.printStackTrace();
}
}, "生产者" + i).start();
}
new Thread(() -> {
while (true) {
Message message = messageQueue.take();
List<String> result = (List<String>) message.getMessage();
log.debug("消息id是{},消息长度是{}\n", message.getId(), result.size());
}
}, "消费者").start();
}
}
@Slf4j(topic = "ch.Message")
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;
}
}
@Slf4j(topic = "ch.MessageQueue")
class MessageQueue {
private LinkedList<Message> queue;
private int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
queue = new LinkedList<>();
}
//消费者方法
public Message take() {
synchronized (queue) {
while (queue.isEmpty()) {
log.debug("队列空了,先等等");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = queue.removeFirst();
queue.notifyAll();
return message;
}
}
//生产者方法
public void put(Message message) {
synchronized (queue) {
while (queue.size() == capacity) {
log.debug("库存已到达上限,wait");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(message);
queue.notifyAll();
}
}
}
park和unpark
基本使用
/ 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
既可以先park再unpark,也可以先unpark再park
特点
与Object的wait¬ify相比
*wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必
*park和unpark是以线程为单位来阻塞和唤醒线程,而notify只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,当然是针对被唤醒对象的等待线程,notify没有unpark精确
*park和unpark可以先unpark再park,但是wait和notify不能先notify
原理之park和unpark
每一个线程都有自己的一个Parker对象,由三部分组成_counter,_cond,_mutex
*调用park方法时,就要做一个判断:
如果_counter为0,那么就进入wait状态
如果_counter不为0,就还是保持运行状态
*调用unpark,就是让_counter不为0
如果这时线程在wait状态,就唤醒让他运行
如果这时线程在运行状态,就把_counter + 1,下次调用park时,因为_counter不为0,所以仍然要运行,这里需要注意,多次调用unpark也只会使得_counter增加1
public static void unparkTest() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("t1 begin");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("park来了");
LockSupport.park();
LockSupport.park();
//执行不到下面这句
log.debug("t1执行结束");
}, "t1");
t1.start();
Thread.sleep(1);
log.debug("unpark");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
log.debug("main end");
}
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现