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内部,且被监控的对象调用。

    原文作者:AnonyPer
    原文地址: https://www.jianshu.com/p/d3214bd34a2d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞