并发学习记录04

wait&notify

wait作用

有时候线程运行时需要用到其他资源如IO,就需要进入IO等待状态,如果这个线程一直占用锁等待的话,其他线程也不能用CPU,效率非常低,所以需要在得到这个锁之后主要调用wait方法,进入waitSet等待,等其他资源满足了调用notify方法唤醒这个线程,让这个线程重新进入竞争锁的队列

wait&notify原理


当前线程发现条件不满足,一般是缺少了某种资源,即可进入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&notify相比
*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");
    }
posted @   理塘DJ  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示