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();
    }
    
  • 相关阅读:
    Office相关
    Eclipse常用设置
    Google logos 纪念电吉他大师莱斯·保罗(LesPaul)演示
    强烈推荐SQL Prompt 3.8,并发布SQL Prompt 3.8 ,SQL Refator 的xxx
    C#命令行编辑器csc.exe
    JSP中文乱码问题 页面经过过滤器后得到的是中文,但插入到MYSQL数据库却成了“?”为什么?
    (转贴)来谈谈SQL数据库中"简单的"SELECT TOP—可能有你从未注意到的细节
    C#Winform限制Textbox只能输入数字
    VPC2007虚拟机与主机的互连互通方法
    邮件会消亡是无稽之谈
  • 原文地址:https://www.cnblogs.com/54chensongxia/p/11995981.html
Copyright © 2011-2022 走看看