美文网首页
Java并发同步与互斥——水果盘问题

Java并发同步与互斥——水果盘问题

作者: biloba | 来源:发表于2018-01-31 13:09 被阅读73次

    Java实现线程间同步与互斥有两种方法:

    1. 关键字synchronized与wait()和notify()/notifyAll()方法相结合实现等待/通知模式
    2. ReentrantLock+Condition对象

    水果盘问题是一个经典的多生产者消费者之间同步与互斥问题:
    家中有一个水果盘,只能放一个水果,爸爸负责放橘子,妈妈放苹果,而儿子只吃苹果,女儿吃橘子。

    下面就分别用以上两种方式解决该问题

    synchronized与wait()和notify()/notifyAll()

    先定义水果和盘子

    enum Fruit{
        APPLE, ORANGE, NONE
    }
    
    class Dish{
        public static Fruit sFruit=Fruit.NONE;
    }
    

    生产者

    class Product{
        private Object lock;
        private Fruit mFruit;//生产者放的水果
    
        Product(Object lock){
            this.lock=lock;
        }
    
        void setFruit(Fruit fruit){
            mFruit=fruit;
        }
    
        void putFruit(){
            synchronized (lock){
                try {
                    String threadName=Thread.currentThread().getName();
    
                    //盘子里有水果则等待
                    while (!Dish.sFruit.equals(Fruit.NONE)){
                        System.out.println(threadName+": Dish is full and I am waiting...");
                        lock.wait();
                    }
    
                    System.out.println(threadName +": Dish is empty and I put an "+mFruit);
                    Dish.sFruit=mFruit;
                    //通知其他所有等待的线程
                    lock.notifyAll();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
    

    消费者

    class Consumer{
        private Object lock;
        private Fruit mFruit;//消费者吃的水果
    
        Consumer(Object lock){
            this.lock=lock;
        }
    
        void setFruit(Fruit fruit){
            mFruit=fruit;
        }
    
        void eatFruit(){
            synchronized (lock){
                try {
                    String threadName=Thread.currentThread().getName();
                    while (!Dish.sFruit.equals(mFruit)){
                        if (Dish.sFruit.equals(Fruit.NONE)) {
                            System.out.println(threadName +": Dish is empty and I am waiting");
                        }else {
                            System.out.println(threadName +": There is no fruit I eat and I am waiting");
                        }
                        lock.wait();
                    }
    
                    System.out.println(threadName +": I get an " +mFruit +" and then eat it.");
                    Dish.sFruit=Fruit.NONE;
                    lock.notifyAll();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    

    生产者线程

    class ProductThread extends Thread{
        private Product mProduct;
    
        ProductThread(Product product, String name, Fruit fruit){
            mProduct=product;
            mProduct.setFruit(fruit);
            setName(name);
        }
    
        @Override
        public void run() {
            while (true){
                mProduct.putFruit();
            }
        }
    }
    

    消费者线程

    class ConsumerThread extends Thread{
        private Consumer mConsumer;
    
        ConsumerThread(Consumer consumer, String name, Fruit fruit){
            mConsumer=consumer;
            mConsumer.setFruit(fruit);
            setName(name);
        }
    
        @Override
        public void run() {
            while (true){
                mConsumer.eatFruit();
            }
        }
    }
    

    main

    public class FruitDish {
        public static void main(String args[]) {
            Object lock=new Object();
            ProductThread father=new ProductThread(new Product(lock), "Father", Fruit.ORANGE);
            ProductThread mather=new ProductThread(new Product(lock), "Mather", Fruit.APPLE);
            ConsumerThread son=new ConsumerThread(new Consumer(lock), "Son", Fruit.APPLE);
            ConsumerThread daughter=new ConsumerThread(new Consumer(lock), "Daughter", Fruit.ORANGE);
    
            father.start();
            mather.start();
            son.start();
            daughter.start();
        }
    }
    

    打印结果

    Father: Dish is empty and I put an ORANGE
    Father: Dish is full and I am waiting...
    Son: There is no fruit I eat and I am waiting
    Mather: Dish is full and I am waiting...
    Daughter: I get an ORANGE and then eat it.
    Daughter: Dish is empty and I am waiting
    Mather: Dish is empty and I put an APPLE
    Mather: Dish is full and I am waiting...
    Son: I get an APPLE and then eat it.
    Son: Dish is empty and I am waiting
    Father: Dish is empty and I put an ORANGE
    Father: Dish is full and I am waiting...
    

    可以看到实现了生产者消费者之间的同步与互斥,但 lock.notifyAll() 会唤醒所有等待的线程,例如爸爸放了一个橘子后,有可能是妈妈,儿子,女儿都被唤醒,这样就白白浪费了时间,能不能爸爸只通知唤醒女儿呢。用wait()和notify()/notifyAll()无法实现,但可以用Condition来做

    Condition

    关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类似ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。

    特殊之处:synchronized相当于整个ReentrantLock对象只有一个单一的Condition对象情况。而一个ReentrantLock却可以拥有多个Condition对象,来实现通知部分线程。

    生产者

    class Product{
        private ReentrantLock lock;
        private Condition mCondition;
        private Fruit mFruit;//生产者放的水果
    
        Product(ReentrantLock lock, Condition condition){
            this.lock=lock;
            mCondition=condition;
        }
    
        void setFruit(Fruit fruit){
            mFruit=fruit;
        }
    
        void putFruit(){
            lock.lock();
            try {
                String threadName=Thread.currentThread().getName();
    
                //盘子里有水果则等待
                while (!Dish.sFruit.equals(Fruit.NONE)){
                    System.out.println(threadName+": Dish is full and I am waiting...");
                    mCondition.await();
                }
    
                System.out.println(threadName +": Dish is empty and I put an "+mFruit);
                Dish.sFruit=mFruit;
                //通知其他所有等待的线程
                mCondition.signalAll();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
    
        }
    }
    

    消费者

    class Consumer{
        private ReentrantLock lock;
        private Condition mCondition;
        private Condition mOtherCondition;
        private Fruit mFruit;//消费者吃的水果
    
        Consumer(ReentrantLock lock,Condition condition, Condition otherCondition){
            this.lock=lock;
            mCondition=condition;
            mOtherCondition=otherCondition;
        }
    
        void setFruit(Fruit fruit){
            mFruit=fruit;
        }
    
        void eatFruit(){
            lock.lock();
            try {
                String threadName=Thread.currentThread().getName();
                while (!Dish.sFruit.equals(mFruit)){
                    if (Dish.sFruit.equals(Fruit.NONE)) {
                        System.out.println(threadName +": Dish is empty and I am waiting");
                    }else {
                        System.out.println(threadName +": There is no fruit I eat and I am waiting");
                    }
                    mCondition.await();
                }
    
                System.out.println(threadName +": I get an " +mFruit +" and then eat it.");
                Dish.sFruit=Fruit.NONE;
                mOtherCondition.signalAll();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
    

    生产者消费者线程未变
    main

    public class FruitDish {
        public static void main(String args[]) {
            ReentrantLock lock=new ReentrantLock();
            Condition[] conditions=new Condition[2];
            conditions[0]=lock.newCondition();
            conditions[1]=lock.newCondition();
    
            ProductThread father=new ProductThread(new Product(lock, conditions[0]), "Father", Fruit.ORANGE);
            ProductThread mather=new ProductThread(new Product(lock, conditions[1]), "Mather", Fruit.APPLE);
            ConsumerThread son=new ConsumerThread(new Consumer(lock, conditions[1], conditions[0]), "Son", Fruit.APPLE);
            ConsumerThread daughter=new ConsumerThread(new Consumer(lock, conditions[0], conditions[1]), "Daughter", Fruit.ORANGE);
    
            father.start();
            mather.start();
            son.start();
            daughter.start();
        }
    }
    

    打印结果

    Mather: Dish is empty and I put an APPLE
    Mather: Dish is full and I am waiting...
    Son: I get an APPLE and then eat it.
    Son: Dish is empty and I am waiting
    Father: Dish is empty and I put an ORANGE
    Father: Dish is full and I am waiting...
    Daughter: I get an ORANGE and then eat it.
    Daughter: Dish is empty and I am waiting
    Mather: Dish is empty and I put an APPLE
    Mather: Dish is full and I am waiting...
    Son: I get an APPLE and then eat it.
    Son: Dish is empty and I am waiting
    

    可以看到打印结果基本很规律,因为父亲女儿共用一个condition,母亲儿子共用一个,父亲放了橘子,只有女儿被通知到,女儿被唤醒吃完了后通知母亲,这样既保证效率高同时也保证了公平(父母间隔放水果)

    相关文章

      网友评论

          本文标题:Java并发同步与互斥——水果盘问题

          本文链接:https://www.haomeiwen.com/subject/xxmkzxtx.html