目录
wait / notify
介绍
① wait:当 Thread3、Thread4 获得锁时,由于没有达到执行条件(Thread3 要锤子,Thread4 要扳手);于是调用 wait,进入左边 WaitSet 等待(等人送工具,没人送会一直等)
② notify:Thread1 来送工具(锤子);调用 notify,在两人中随机叫了一个;叫的是 Thread4 ,他进入 EntryList ;再次获得锁时,发现送来的工具是锤子,又干不了活 ......
③ notifyAll:Thread1 送工具时,把所有人都叫去 EntryList
综上:wait 在线程获取锁,但是不符合执行条件时调用;notify 只会随机唤醒一个线程;notifyAll 会唤醒所有线程
wait 与 sleep 对比
相同点:都会阻塞、释放时间片
不同点:① wait 会将锁释放,sleep 不会② sleep 是 Thread 方法,wait 是 Object 方法 ③ wait(无参)需要被唤醒;sleep 时间到了,自动解除阻塞
1. lazy 拿到锁,不干活,睡觉;所有人都要等着他醒来
@Slf4j(topic = "c.WaitAndSleep") public class WaitAndSleep { public static void main(String[] args) { sleepTest(); } static void sleepTest(){ new Thread(() -> { synchronized (WaitAndSleep.class){ log.debug("懒得动,睡觉......"); // 懒狗,拿到锁,不干活 try { Thread.sleep(3000); log.debug("唉,还没睡够......"); } catch (InterruptedException e) { e.printStackTrace(); } } }, "lazy").start(); for(int i = 0; i < 5; i++){ new Thread(() -> { synchronized (WaitAndSleep.class){ log.debug("赶紧干完活,下班!"); } }, "t" + i).start(); } } }
2. smart 虽然很累,但是不想给他人添麻烦,于是到隔壁 WaitSet 休息;等人叫醒(可能没人叫),再去 EntryList 竞争锁
@Slf4j(topic = "c.WaitAndSleep") public class WaitAndSleep { public static void main(String[] args) { waitTest(true); // waitTest(false); } static void waitTest(boolean isNotify){ new Thread(() -> { synchronized (WaitAndSleep.class){ log.debug("有点累,去隔壁WaitSet休息一下......"); try { WaitAndSleep.class.wait(); log.debug("有人叫我了,该去EntryList了......"); } catch (InterruptedException e) { e.printStackTrace(); } } }, "smart").start(); for(int i = 0; i < 5; i++){ int index = i; new Thread(() -> { synchronized (WaitAndSleep.class){ log.debug("赶紧干完活,下班!"); if(index == 4 && isNotify){ WaitAndSleep.class.notify(); // 已经知道只有 smart 在 WaitSet } } }, "t" + index).start(); } } }
有人叫
没人叫,会一直阻塞
3. smart 学聪明了;不能像上次一样,一直不醒了;定了个闹钟,就算没人叫,也能醒来
WaitAndSleep.class.wait(3000); log.debug("闹钟响了,该去EntryList了......");
保护性暂停(join 原理)
定义
Guarded Suspension,用在一个线程等待另一个线程的执行结果;该为同步模式
① 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 Guarded Object
② JDK 中,join、Future 的实现,采用的是此模式
③ 因为要等待另一方的结果,因此归类到同步模式
实现
public class GuardedObject { private Object value; public Object getValue() throws InterruptedException { synchronized (this){ // wait / notify while(value == null){ this.wait(); } return this.value; } } public void setValue(Object value) { synchronized (this){ this.value = value; this.notifyAll(); } } } @Slf4j(topic = "c.Test11") class Test11{ public static void main(String[] args) { GuardedObject go = new GuardedObject(); new Thread(() -> { try { log.debug("尝试获取 go 的值..."); Object value = go.getValue(); log.debug("获取成功: " + value); } catch (InterruptedException e) { e.printStackTrace(); } }, "t1").start(); new Thread(() -> { try { log.debug("三秒后完成任务"); Thread.sleep(3000); go.setValue("go"); } catch (InterruptedException e) { e.printStackTrace(); } }, "t2").start(); } }
join 实现(带超时的 Guarded Object)
public Object getValue(long waitTime) throws InterruptedException { synchronized (this){ // wait / notify long start = System.currentTimeMillis(); // 开始等待的时间点 long hasWaited = 0; // 已经等待了多久 while(value == null){ long restWaitTime = waitTime - hasWaited; // 剩余需要等待的时间 if(restWaitTime <= 0){ // 小于 0 结束等待 log.debug("即将退出,value 此时还是 null 吗? {}", value == null); // 超时时打印 break; } log.debug("value 依旧为 null,{}s 后,自动退出", restWaitTime); this.wait(restWaitTime); hasWaited = System.currentTimeMillis() - start; // 更新已经等待的时间 } return this.value; } }
未超时情况
@Slf4j(topic = "c.Test11") class Test11{ public static void main(String[] args) { GuardedObject go = new GuardedObject(); new Thread(() -> { try { log.debug("尝试获取 value 的值,不想等太久..."); Object value = go.getValue(5000); log.debug("已经退出了,查看 value 值: " + value); } catch (InterruptedException e) { e.printStackTrace(); } }, "t1").start(); new Thread(() -> { try { // 过一秒再给值 Thread.sleep(1000); log.debug("过了一秒了,给你 value: null"); go.setValue(null); // 过一秒再给值 Thread.sleep(1000); log.debug("又过一秒了,给你 value: null"); go.setValue(null); // 过一秒再给值 Thread.sleep(1000); log.debug("又又过一秒,给你 value: go"); go.setValue("go"); } catch (InterruptedException e) { e.printStackTrace(); } }, "t2").start(); } }
超时情况
Object value = go.getValue(2000);
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) { // 为 0 while (isAlive()) { // 当前线程还存活 wait(0); // 一直等 } } else { while (isAlive()) { long delay = millis - now; // 剩余等待时间 if (delay <= 0) { // 剩余等待时间小于 0 则不再等待 break; } wait(delay); // 执行等待 now = System.currentTimeMillis() - base; // 更新已经等待时间 } } }
生产者/消费者(阻塞队列)
定义
① 不需要产生结果和消费结果的线程一一对应
② 消费队列可以用来平衡生产和消费的线程资源(没被消费的,先暂存)
③ 生产者只负责生产消息,消费者只负责对消息的处理
④ JDK 各种阻塞队列都是用的这种模式(空了,就不消费;满了,就不生产)
⑤ 此方法为异步
实现
@Slf4j(topic = "c.Test10") class Test10{ public static void main(String[] args) { MessageQueue mq = new MessageQueue(2); for(int i = 0; i < 5; i++){ int index = i; new Thread(() -> { mq.put(new MyMessage(index, "消息" + index)); }, "生产者" + i).start(); } new Thread(() -> { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } MyMessage message = mq.take(); log.debug("消费一条消息: " + message); } }, "消费者").start(); } } @Slf4j(topic = "c.MessageQueue") public class MessageQueue { private LinkedList<MyMessage> mq = new LinkedList(); private int capacity; private int size; public MessageQueue(int capacity){ this.capacity = capacity; } public MyMessage take(){ synchronized (mq){ while(size == 0){ try { log.debug("还没有 message 可消费......"); mq.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } size--; mq.notifyAll(); return mq.pollFirst(); } } public void put(MyMessage newMessage){ synchronized (mq){ while(size >= capacity){ log.debug("空间已满......"); try { mq.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } size++; mq.add(newMessage); log.debug("生产一条信息消息: " + newMessage); mq.notifyAll(); } } } class MyMessage{ private int id; private Object value; public MyMessage(int id, Object value) { this.id = id; this.value = value; } public int getId() { return id; } public Object getValue() { return value; } @Override public String toString() { return "MyMessage{" + "id=" + id + ", value=" + value + '}'; } }