wait、notify、notifyAll以及Condition的await、signal,signalAll的用法

wait、notify、notifyAll 以及Condition的await、signal,signalAll的用法

线程之间同步可以使用synchronized关键词修饰,也可以使用Lock锁来实现。对应synchronized的有Object类下面的wait、notify、notifyall方法以及ReentrantLock类相关的Condition接口(接口有await、signal、signalAll方法),那么他们怎么使用以及有什么关系和区别呢?我们通过一个例子来讲解一下:

某小工厂是买一款机器产品的,它只有两个部门(一个部门同一时刻只能做一件事),一个部门是生产产品,另一个部门是销售产品,但是该工厂仓库有限,同时只能放一个产品。这种情况下,生产部门和销售部门间的协作会有以下几种:

1、生产部门生产产品时,发现仓库有机器,就需要暂停生产流程,等待销售卖产品。

2、生产部门生产产品时,发现仓库没有机器,开始生产,完成后通知销售部门卖产品。

3、销售部门销售产品时,发现仓库有机器,卖出之后,通知生产部门准备机器。

4、销售部门销售产品时,发现仓库没有机器,先暂停销售流程,等待生产部门生产产品。

通过代码的形式就如下:

package com.test.java;

/**
 * TestApplication
 * Created by anonyper on 2019/4/10.
 */

//小工厂
public class SmallFactory {
    /**
     * 仓库
     */
    static class Warehouse {
        boolean hasProduct = false;//代表仓库有没有产品

        /**
         * 生产产品
         */
        public synchronized void createProduct(int index) throws InterruptedException {
            while (hasProduct) {
                System.out.println("生产时仓库有产品,等待 "+index);
                wait();
            }
            System.out.println("生产了一个产品 "+index);
            hasProduct = true;
            notify();//通知可以买产品了
        }

        /**
         * 销售产品
         */
        public synchronized void sellProduct(int index) throws InterruptedException {
            while (!hasProduct) {
                System.out.println("售卖时仓库没有产品,等待 "+index);
                wait();
            }
            System.out.println("卖出了一个产品 "+index);
            hasProduct = false;
            notify();//通知可以买产品了
        }
    }


    /**
     * 生产部门
     */
    static class CreateDepartment implements Runnable {
        Warehouse warehouse;//和仓库关联

        public CreateDepartment(Warehouse warehouse) {
            this.warehouse = warehouse;
        }

        @Override
        public void run() {
            for (int i = 0; i <10 ; i++) {
                try {
                    this.warehouse.createProduct(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 销售部门
     */
    static class SellDepartment implements Runnable {
        Warehouse warehouse;//和仓库关联

        public SellDepartment(Warehouse warehouse) {
            this.warehouse = warehouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    this.warehouse.sellProduct(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        Warehouse warehouse = new Warehouse();
        new Thread(new CreateDepartment(warehouse)).start();
        new Thread(new SellDepartment(warehouse)).start();
    }
}

//运行结果
生产了一个产品 0
生产时仓库有产品,等待 1
卖出了一个产品 0
售卖时仓库没有产品,等待 1
生产了一个产品 1
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 2
生产时仓库有产品,等待 3
卖出了一个产品 2
生产了一个产品 3
生产时仓库有产品,等待 4
卖出了一个产品 3
售卖时仓库没有产品,等待 4
生产了一个产品 4
卖出了一个产品 4
售卖时仓库没有产品,等待 5
生产了一个产品 5
生产时仓库有产品,等待 6
卖出了一个产品 5
售卖时仓库没有产品,等待 6
生产了一个产品 6
卖出了一个产品 6
售卖时仓库没有产品,等待 7
生产了一个产品 7
卖出了一个产品 7
售卖时仓库没有产品,等待 8
生产了一个产品 8
卖出了一个产品 8
售卖时仓库没有产品,等待 9
生产了一个产品 9
卖出了一个产品 9

Process finished with exit code 0

通过ReentrantLock同步锁的实现代码如下:

package com.test.java;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * TestApplication
 * Created by anonyper on 2019/4/10.
 */

//小工厂
public class SmallFactory {
    /**
     * 仓库
     */
    static class Warehouse {
        boolean hasProduct = false;//代表仓库有没有产品

        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();
        /**
         * 生产产品
         */
        public void createProduct(int index) throws InterruptedException {
            try{
                reentrantLock.lock();
                while (hasProduct) {
                    System.out.println("生产时仓库有产品,等待 "+index);
//                    wait();
                    condition.await();
                }
                System.out.println("生产了一个产品 "+index);
                hasProduct = true;
//                notify();//通知可以买产品了
                condition.signal();
            }finally {
                reentrantLock.unlock();
            }

        }

        /**
         * 销售产品
         */
        public void sellProduct(int index) throws InterruptedException {
            try {
                reentrantLock.lock();
                while (!hasProduct) {
                    System.out.println("售卖时仓库没有产品,等待 "+index);
//                    wait();
                    condition.await();
                }
                System.out.println("卖出了一个产品 "+index);
                hasProduct = false;
//                notify();//通知可以买产品了
                condition.signal();
            }finally {
                reentrantLock.unlock();
            }

        }
    }


    /**
     * 生产部门
     */
    static class CreateDepartment implements Runnable {
        Warehouse warehouse;//和仓库关联

        public CreateDepartment(Warehouse warehouse) {
            this.warehouse = warehouse;
        }

        @Override
        public void run() {
            for (int i = 0; i <10 ; i++) {
                try {
                    this.warehouse.createProduct(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 销售部门
     */
    static class SellDepartment implements Runnable {
        Warehouse warehouse;//和仓库关联

        public SellDepartment(Warehouse warehouse) {
            this.warehouse = warehouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    this.warehouse.sellProduct(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        Warehouse warehouse = new Warehouse();
        new Thread(new CreateDepartment(warehouse)).start();
        new Thread(new SellDepartment(warehouse)).start();
    }
}

//执行结果:

生产了一个产品 0
生产时仓库有产品,等待 1
卖出了一个产品 0
售卖时仓库没有产品,等待 1
生产了一个产品 1
生产时仓库有产品,等待 2
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 2
生产时仓库有产品,等待 3
卖出了一个产品 2
售卖时仓库没有产品,等待 3
生产了一个产品 3
生产时仓库有产品,等待 4
卖出了一个产品 3
售卖时仓库没有产品,等待 4
生产了一个产品 4
生产时仓库有产品,等待 5
卖出了一个产品 4
售卖时仓库没有产品,等待 5
生产了一个产品 5
生产时仓库有产品,等待 6
卖出了一个产品 5
生产了一个产品 6
生产时仓库有产品,等待 7
卖出了一个产品 6
生产了一个产品 7
生产时仓库有产品,等待 8
卖出了一个产品 7
售卖时仓库没有产品,等待 8
生产了一个产品 8
生产时仓库有产品,等待 9
卖出了一个产品 8
售卖时仓库没有产品,等待 9
生产了一个产品 9
卖出了一个产品 9

Process finished with exit code 0

总结:

通过例子,我们可以看到synchronized是同步代码块,给线程加锁,统一时刻只能生产或者销售产品,wait是让当前线程处于等待过程,同时释放线程锁,notify唤醒阻塞的线程,继续执行。

  • synchronized下的wait对应Condtion的await,让当前线程阻塞并释放线程锁的作用。
  • synchronized下的notify对应Condtion的signal,随机唤醒阻塞线程继续执行。
  • synchronized下的notifyAll对应Condtion的signalAll,唤醒所有阻塞线程继续执行。

上述只有两个部门,就意味着只有两个线程在运行,所以一个运行唤醒阻塞的必然是另一个,这种情况我们看到synchronized和ReentrantLock除了书写不一样,没什么使用上的区别。

现在这个小工厂因为效益好,扩大规模了,成立了5个生产部门和5个销售部门,但是仓库还是只有一个(多个其实是一样的),main方法中的代码改动如下:

public static void main(String[] args) {
    Warehouse warehouse = new Warehouse();
    for (int i = 0; i <5 ; i++) {
        new Thread(new CreateDepartment(warehouse)).start();
        new Thread(new SellDepartment(warehouse)).start();
    }
    
}

这个时候不管是synchronized也好,还是上面的Condtion,都可能出现下面的运行结果:

生产了一个产品 0
生产时仓库有产品,等待 0
卖出了一个产品 0
售卖时仓库没有产品,等待 1
生产了一个产品 1
生产时仓库有产品,等待 2
生产时仓库有产品,等待 0
卖出了一个产品 0
售卖时仓库没有产品,等待 0
生产了一个产品 0
生产时仓库有产品,等待 1
生产时仓库有产品,等待 0
生产时仓库有产品,等待 0
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 2
生产时仓库有产品,等待 3
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 0
生产时仓库有产品,等待 1
卖出了一个产品 0
售卖时仓库没有产品,等待 1
售卖时仓库没有产品,等待 0
售卖时仓库没有产品,等待 0
生产了一个产品 1
生产时仓库有产品,等待 0
生产时仓库有产品,等待 0
卖出了一个产品 2
生产了一个产品 3
生产时仓库有产品,等待 4
生产时仓库有产品,等待 2
卖出了一个产品 2
售卖时仓库没有产品,等待 3
售卖时仓库没有产品,等待 3
生产了一个产品 1
生产时仓库有产品,等待 2
卖出了一个产品 1
售卖时仓库没有产品,等待 2
售卖时仓库没有产品,等待 0
售卖时仓库没有产品,等待 0

  
//线程阻塞了

为什么会这样呢?因为notify和signal是随机唤醒一个阻塞的线程,当多个线程都在运行时,如上运行结果就是售卖线程卖出了之后,随机唤醒的线程还是销售的线程,这个时候进入阻塞之后就没有人可以唤醒了,这就是死锁情况。

怎么解决呢?简单粗暴的方法就是随机唤醒一个线程可能出错,那么我将所有阻塞的线程都唤醒不就可以了么,将上面的notify替换成notifyAll或者signal替换成signalAll,线程也就不会死锁了。(这也是判断用while不用if的原因)。

上面的解决方案虽然可行,但是不太合理,那么有没有针对性的唤醒呢?比如销售线程唤醒的就是生产线程,生产线程唤醒的就是销售线程,那么这个问题就解决了吧。这里就要用到Condtion来精确控制了。

同一个ReentrantLock对象可以实例化不同的Condtion对象,通过对Condtion唤醒的控制,可以实现不通过线程的精确唤醒:

package com.test.java;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * TestApplication
 * Created by anonyper on 2019/4/10.
 */

//小工厂
public class SmallFactory {
    /**
     * 仓库
     */
    static class Warehouse {
        boolean hasProduct = false;//代表仓库有没有产品

        ReentrantLock reentrantLock = new ReentrantLock();
        Condition createCondition = reentrantLock.newCondition();//控制生产
        Condition sellCondition = reentrantLock.newCondition();//控制销售
        /**
         * 生产产品
         */
        public void createProduct(int index) throws InterruptedException {
            try{
                reentrantLock.lock();
                while (hasProduct) {
                    System.out.println("生产时仓库有产品,等待 "+index);
//                    wait();
                    createCondition.await();
                }
                System.out.println("生产了一个产品 "+index);
                hasProduct = true;
//                notify();//通知可以买产品了
                sellCondition.signal();
            }finally {
                reentrantLock.unlock();
            }

        }

        /**
         * 销售产品
         */
        public void sellProduct(int index) throws InterruptedException {
            try {
                reentrantLock.lock();
                while (!hasProduct) {
                    System.out.println("售卖时仓库没有产品,等待 "+index);
//                    wait();
                    sellCondition.await();
                }
                System.out.println("卖出了一个产品 "+index);
                hasProduct = false;
//                notify();//通知可以买产品了
                createCondition.signal();
            }finally {
                reentrantLock.unlock();
            }

        }
    }


    /**
     * 生产部门
     */
    static class CreateDepartment implements Runnable {
        Warehouse warehouse;//和仓库关联

        public CreateDepartment(Warehouse warehouse) {
            this.warehouse = warehouse;
        }

        @Override
        public void run() {
            for (int i = 0; i <10 ; i++) {
                try {
                    this.warehouse.createProduct(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 销售部门
     */
    static class SellDepartment implements Runnable {
        Warehouse warehouse;//和仓库关联

        public SellDepartment(Warehouse warehouse) {
            this.warehouse = warehouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    this.warehouse.sellProduct(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        Warehouse warehouse = new Warehouse();
        for (int i = 0; i <5 ; i++) {
            new Thread(new CreateDepartment(warehouse)).start();
            new Thread(new SellDepartment(warehouse)).start();
        }

    }
}

以上就是线程通信涉及到的wait、notify、notifyAll以及Condition的await、signal,signalAll的用法。

ps:

wait、notify、notifyAll 只能用在synchronized内部,且被监控的对象调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 一、进程和线程 进程 进程就是一个执行中的程序实例,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。...
    阿敏其人阅读 2,566评论 0 13
  • 【JAVA 线程】 线程 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者...
    Rtia阅读 2,702评论 2 20
  • 五月正是插秧期, 机器插秧造价低。 苗齐垄直绿成行, 解放妇女喜洋洋。 稀适株距多产粮, 一年更比一年强。
    笑口常开_6135阅读 431评论 2 5
  • 1-我们的外在命运和我们的内在想象,也就是内在意识,是镜像的关系。你可以通过一个人的外在命运,看到他的内在想象,也...
    依诺芝阅读 507评论 0 1