zoukankan      html  css  js  c++  java
  • 【并发编程】Object的wait、notify和notifyAll方法


    本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

    并发编程系列博客传送门


    方法简介

    wait方法

    当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起(进入waiting状态),直到发生下面几件事情之一才能返回:

    • 其他线程调用了该共享对象的notify()或者notifyAll()方法;
    • 其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。

    另外需要注意的是,如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出IllegalMonitorStateException异常。如果当前线程已经获取了锁资源,调用wait方法之后会释放这个锁资源,但是只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的

    wait方法还有一个重载方法wait(long time),这个方法会等待time时间,如果在这个时间内没有其他线程来唤醒它的话,这个线程会自己唤醒继续获得执行机会。

    notify方法

    notify方法会唤醒等待对象监视器的单个线程,如果等待对象监视器的有多个线程,则选取其中一个线程进行唤醒,到底选择唤醒哪个线程是任意的,由CPU自己决定。如果没有再调用notify方法,其他阻塞的线程可能就永远得不到再执行的机会了。

    此外,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。

    一个还需要注意的地方是,在共享变量上调用notifyAll()方法只会唤醒调用这个方法前调用了wait系列函数而被放入共享变量等待集合里面的线程。如果调用notifyAll()方法后一个线程调用了该共享变量的wait()方法而被放入阻塞集合,则该线程是不会被唤醒的

    类似wait系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的notify()方法,否则会抛出IllegalMonitorStateException异常。

    notify方法还有个兄弟方法notifyAll,这个方法会唤醒所有等待监视器对象的线程。

    wait-notify模式的典型应用

    wait-notify模式的一个典型应用就是可以实现生产者-消费者模式。让我印象很深是我毕业那年阿里巴巴校园招聘的一个笔试题:

    有一个苹果箱,有10个人向这个箱子中每次随机放入一个苹果,有10个人每次随机从这个箱子中随机拿走一个苹果,同时需要满足箱子中的苹果总数不能超过50个。请用代码实现上面的场景(不能使用并发集合框架)

    现在看来,这道题不就是为wait-notify模式量身打造的一道题目么。当时水平有限,又急急忙忙的,所以记得当时写的不太好。这边重新整理下这个代码

    
    public class AppleBox {
    
        private int appleCount;
    
        public synchronized void putApple() {
            while (appleCount >= 50) {
                try {
                    //会释放锁
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            appleCount++;
            String name = Thread.currentThread().getName();
            System.out.println("[" + name + "]放入一个,当前盒子中苹果数:" + appleCount);
            this.notifyAll();
        }
    
    
        public synchronized void takeApple() {
            while (appleCount <= 0) {
                try {
                    //会释放锁
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            appleCount--;
            String name = Thread.currentThread().getName();
            System.out.println("[" + name + "]拿走一个,当前盒子中苹果数:" + appleCount);
            this.notifyAll();
        }
    
        private static class AppleTaker implements Runnable {
    
            private AppleBox appleBox;
    
            public AppleTaker(AppleBox appleBox) {
                this.appleBox = appleBox;
            }
    
            @Override
            public void run() {
                while (true) {
                    appleBox.takeApple();
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
        private static class ApplePutter implements Runnable {
    
            private AppleBox appleBox;
    
            public ApplePutter(AppleBox appleBox) {
                this.appleBox = appleBox;
            }
    
            @Override
            public void run() {
                while (true) {
                    appleBox.putApple();
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
        public static void main(String[] args) {
            AppleBox appleBox = new AppleBox();
    
            for (int i = 0; i < 20; i++) {
                Thread t = new Thread(new ApplePutter(appleBox));
                t.setName("ApplePutter:" + i);
                t.start();
            }
    
            for (int i = 0; i < 20; i++) {
                Thread t = new Thread(new AppleTaker(appleBox));
                t.setName("AppleTaker:" + i);
                t.start();
            }
    
        }
    }
    
    

    执行结果如下:

    
    [ApplePutter:0]放入一个,当前盒子中苹果数:1
    [ApplePutter:1]放入一个,当前盒子中苹果数:2
    [ApplePutter:5]放入一个,当前盒子中苹果数:3
    [ApplePutter:9]放入一个,当前盒子中苹果数:4
    [ApplePutter:13]放入一个,当前盒子中苹果数:5
    [ApplePutter:2]放入一个,当前盒子中苹果数:6
    [ApplePutter:6]放入一个,当前盒子中苹果数:7
    [ApplePutter:10]放入一个,当前盒子中苹果数:8
    [ApplePutter:17]放入一个,当前盒子中苹果数:9
    [ApplePutter:14]放入一个,当前盒子中苹果数:10
    [ApplePutter:18]放入一个,当前盒子中苹果数:11
    [ApplePutter:3]放入一个,当前盒子中苹果数:12
    [ApplePutter:7]放入一个,当前盒子中苹果数:13
    [ApplePutter:11]放入一个,当前盒子中苹果数:14
    [ApplePutter:8]放入一个,当前盒子中苹果数:15
    [ApplePutter:15]放入一个,当前盒子中苹果数:16
    [ApplePutter:19]放入一个,当前盒子中苹果数:17
    [ApplePutter:4]放入一个,当前盒子中苹果数:18
    [AppleTaker:3]拿走一个,当前盒子中苹果数:17
    [ApplePutter:12]放入一个,当前盒子中苹果数:18
    [AppleTaker:1]拿走一个,当前盒子中苹果数:17
    [AppleTaker:5]拿走一个,当前盒子中苹果数:16
    [ApplePutter:16]放入一个,当前盒子中苹果数:17
    [AppleTaker:0]拿走一个,当前盒子中苹果数:16
    [AppleTaker:12]拿走一个,当前盒子中苹果数:15
    [AppleTaker:8]拿走一个,当前盒子中苹果数:14
    [AppleTaker:16]拿走一个,当前盒子中苹果数:13
    [AppleTaker:7]拿走一个,当前盒子中苹果数:12
    [AppleTaker:11]拿走一个,当前盒子中苹果数:11
    [AppleTaker:19]拿走一个,当前盒子中苹果数:10
    [AppleTaker:9]拿走一个,当前盒子中苹果数:9
    [AppleTaker:13]拿走一个,当前盒子中苹果数:8
    [AppleTaker:2]拿走一个,当前盒子中苹果数:7
    [AppleTaker:6]拿走一个,当前盒子中苹果数:6
    [AppleTaker:10]拿走一个,当前盒子中苹果数:5
    [AppleTaker:14]拿走一个,当前盒子中苹果数:4
    [AppleTaker:4]拿走一个,当前盒子中苹果数:3
    [AppleTaker:15]拿走一个,当前盒子中苹果数:2
    [AppleTaker:18]拿走一个,当前盒子中苹果数:1
    [AppleTaker:17]拿走一个,当前盒子中苹果数:0
    [ApplePutter:0]放入一个,当前盒子中苹果数:1
    [ApplePutter:1]放入一个,当前盒子中苹果数:2
    [ApplePutter:5]放入一个,当前盒子中苹果数:3
    [ApplePutter:9]放入一个,当前盒子中苹果数:4
    [ApplePutter:13]放入一个,当前盒子中苹果数:5
    [ApplePutter:17]放入一个,当前盒子中苹果数:6
    [ApplePutter:2]放入一个,当前盒子中苹果数:7
    [ApplePutter:6]放入一个,当前盒子中苹果数:8
    [ApplePutter:10]放入一个,当前盒子中苹果数:9
    [ApplePutter:14]放入一个,当前盒子中苹果数:10
    [ApplePutter:18]放入一个,当前盒子中苹果数:11
    [ApplePutter:3]放入一个,当前盒子中苹果数:12
    [ApplePutter:7]放入一个,当前盒子中苹果数:13
    [ApplePutter:11]放入一个,当前盒子中苹果数:14
    [ApplePutter:15]放入一个,当前盒子中苹果数:15
    [ApplePutter:19]放入一个,当前盒子中苹果数:16
    [AppleTaker:3]拿走一个,当前盒子中苹果数:15
    [ApplePutter:4]放入一个,当前盒子中苹果数:16
    [ApplePutter:8]放入一个,当前盒子中苹果数:17
    [ApplePutter:12]放入一个,当前盒子中苹果数:18
    
    

    **PS: 多线程编程中,最要的重要的两点是先抽象出共享变量是什么,任务类(Runner)是什么 **

    wait-notify模式的经典写法

    生产者和消费者的逻辑都可以统一抽象成以下几个步骤:

    • step1:获得对象的锁;
    • step2:循环判断是否需要进行生产活动,如果不需要进行生产就调用wait方法,暂停当前线程;如果需要进行生产活动,进行对应的生产活动;
    • step3:通知等待线程

    伪代码如下:

    synchronized(对象) {
        //这边进行循环判断的原因是为了防止伪唤醒,也就是不是消费线程或者生产线程调用notify方法将waiting线程唤醒的
        while(条件){
            对象.wait();
        }
        //进行生产或者消费活动
        doSomething();
        对象.notifyAll();
    }
    
  • 相关阅读:
    Binary Search Tree Iterator 解答
    Invert Binary Tree 解答
    Min Stack 解答
    Trapping Raining Water 解答
    Candy 解答
    Jump Game II 解答
    Implement Hash Map Using Primitive Types
    Gas Station 解答
    Bucket Sort
    HashMap 专题
  • 原文地址:https://www.cnblogs.com/54chensongxia/p/11995981.html
Copyright © 2011-2022 走看看