虚假唤醒
虚假唤醒
class A { public static void main(String[] args) { Data data = new Data(); // +1 new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); // -1 new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); } } class Data { private int num = 0; // +1 public synchronized void increment() throws InterruptedException { if (num != 0) { this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "->" + num); this.notifyAll(); } // -1 public synchronized void decrement() throws InterruptedException { if (num == 0) { this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "->" + num); this.notifyAll(); } }
- jdk官方文档
- 当有更多线程时就会出现虚假唤醒,把if改成while避免虚假唤醒。等待应该总是出现在循环中
class A { public static void main(String[] args) { Data data = new Data(); // 生产者 new Thread(() -> { for (int i = 0; i < 5; i++) { try { data.produce(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); // 生产者 new Thread(() -> { for (int i = 0; i < 5; i++) { try { data.produce(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); // 消费者 new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(10); data.consume(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); // 消费者 new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(20); data.consume(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); } } class Data { private int num = 0; // 当前缓冲池中数量 private int maxSize = 3; // 缓冲池最大放3个 // 生产一个 // 关键字在实例方法上,锁为当前实例(当关键字在静态方法上,锁为当前Class对象) public synchronized void produce() throws InterruptedException { // 出现虚假唤醒的原因是从阻塞态到就绪态再到运行态没有进行判断,需要让其每次得到操作权时都进行判断 if (num >= maxSize) { // 改成while缓冲池就不会溢出 System.out.println(Thread.currentThread().getName() + "被阻塞在wait()"); this.wait(); // 提前释放synchronized锁,重新去请求锁导致的阻塞 } num++; System.out.println(Thread.currentThread().getName() + "生产1个后剩余" + num); this.notifyAll(); // notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁 } // 消费1个 public synchronized void consume() throws InterruptedException { if (num <= 0) { // 改成while缓冲池就不会溢出 this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "消费1个后剩余" + num); this.notifyAll(); } } /* // 如果一开始是消费者抢到锁,由于num=0,会执行wait()。被阻塞在wait(),进入等待队列。 A生产1个后剩余1 // 生产者A先抢到锁,开始生产。(BCD试图竞争锁,都会被阻塞在同步代码块开始处,进入等待队列) // 生产一个后调用notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。 // 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。 A生产1个后剩余2 // 又是A抢到锁,BCD还是阻塞在各自同步代码块开始处,进入等待队列。 // 生产一个后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。 // 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。 A生产1个后剩余3 // 又是A抢到锁,BCD还是阻塞在各自同步代码块开始处,进入等待队列。 // 生产一个后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。 // 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。 // 缓冲区满。若生产者A(B同理)抢到锁,则执行wait(),提前释放锁,自己被阻塞在wait()处,进入等待队列。 // 然后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列去竞争锁。 // 如果此时生产者B去竞争锁,也会因为num >= maxSize而调用wait()进入等待队列,这次被阻塞在wait()处。 A被阻塞在wait() // AB都阻塞在wait() B被阻塞在wait() C消费1个后剩余2 // 在同步队列中的消费者CD去竞争锁,C抢到,D阻塞在同步代码块开始处,进入等待队列。 // C消费一个后去notifyALl()唤醒了ABD。并在同步代码块结束后释放锁。 // 同步队列中ABCD一起竞争锁。 A生产1个后剩余3 // A抢到锁,BCD阻塞在原来位置(B在wait()处,CD在同步代码块开始处),进入等待队列。 // 生产一个后notifyAll()唤醒在等待队列中的消费者CD和生产者B,BCD进入同步队列。 // 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。 A被阻塞在wait() // 缓冲区满。A又抢到了锁,因为num >= maxSize被阻塞在wait() B生产1个后剩余4 // 生产者B抢到锁,从之前被阻塞的wait()往后执行,不经过if判断,num+1。(此时CD阻塞在各自同步代码块处,A在wait()) // 然后notifyAll()唤醒在等待队列中的消费者CD和生产者A,ACD进入同步队列去竞争锁。 B被阻塞在wait() // B又抢到了锁,因为num >= maxSize被阻塞在wait()。 A生产1个后剩余5 // A抢到锁,从wait()后直接执行,不经过if判断,num+1。 // 然后notifyAll()唤醒在等待队列中的消费者CD和生产者B,ABCD进入同步队列。 // 同步代码块执行完synchronized会自动释放锁。同步队列中ABCD一起竞争锁。 B生产1个后剩余6 // B抢到锁,从之前被阻塞的wait()往后执行,不经过if判断,num+1。(此时ACD都阻塞在同步代码块开始处) B被阻塞在wait() // B抢到了锁,因为num >= maxSize被阻塞在wait() // ...... C消费1个后剩余5 B生产1个后剩余6 B被阻塞在wait() C消费1个后剩余5 B生产1个后剩余6 B被阻塞在wait() C消费1个后剩余5 B生产1个后剩余6 C消费1个后剩余5 C消费1个后剩余4 C消费1个后剩余3 C消费1个后剩余2 C消费1个后剩余1 C消费1个后剩余0 */
本文作者:n1ce2cv
本文链接:https://www.cnblogs.com/sprinining/p/15348396.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步