生产者与消费者从synchronized到ReentantLock

生产者消费者问题是经典的多线程问题,是工业化生产的模型,今天讲的就是生产者,消费者模型以及如何慢慢的改进,进而变为多消费者,多生产者。首先从简单的开始做起,假设我们只有一个生产者,一个消费者。代码实现如下

plate代表着共享区,生产者和消费者可以同时操作共享区,分别生产蛋和消费蛋

**
 * Created by lin on 2018/9/15.
 */
public class Plate {

    private List<Object> eggs = new ArrayList<>();
    private int capacity;

    public Plate(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("容量不能小于0");
        }
        this.capacity = capacity;
    }

    public Object getEgg() {

        System.out.println("消费者取得蛋");
        Object egg = eggs.get(0);
        eggs.remove(0);
        return egg;


    }

    public void addEgg(Object egg) {
        System.out.println("生产者生产蛋");
        eggs.add(egg);
    }

    public int getEggNum() {

        return eggs.size();
    }

    public boolean isFull() {
        return this.eggs.size() >= this.capacity;
    }

    public boolean isEmpty() {
        return this.eggs.isEmpty();
    }

}

 

 

package Inter;

/** 消费者,
 * Created by lin on 2018/9/15.
 */
public class Consumer implements Runnable {

    private Plate plate;

    public Consumer(Plate plate) {
        this.plate = plate;
    }

    @Override
    public void run() {
         while(true)
            synchronized (plate) {

                //当容量小于0时,进入等待队列
                if (plate.isEmpty()) { 

                    try {
                        plate.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }

                plate.getEgg();
                plate.notify();

}
            

        }
    }
}
package Inter;

/** 生产者
 * Created by lin on 2018/9/15.
 */
public class Producer implements Runnable {
    private Plate plate;

    public Producer(Plate plate) {

        this.plate = plate;

    }


    @Override
    public void run() {
      while(true)
            synchronized (plate) {

                if (plate.isFull()) {

                    try {
                        plate.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                Object egg = new Object();
                plate.addEgg(egg);
                plate.notify();

            }
        }
    }
}

测试类如下

/**
 * Created by lin on 2018/9/15.
 */
public class Tester {

    /*
     * @param args
     */
    public static void main(String[] args) {
        Plate plate = new Plate(20);

        Thread t1 = new Thread(new Consumer(plate));
        t1.setName("CousumerThread-" + 1);
        Thread t2 = new Thread(new Producer(plate));
        t2.setName("ProducerThread-" + 2);
        t1.start();
        t2.start();
       


    }

单消费者,单生产者,上面的代码是没有问题的,并且能够很好的运行。

假设我们使用多生产者,多消费者呢?上面的生产者和消费者代码是否有问题?,问题出在哪?

多生产者,多消费者的测试类代码实现如下

package Inter;

/**
 * Created by lin on 2018/9/15.
 */
public class Tester {

   
     * @param args
     */
    public static void main(String[] args) {
        Plate plate = new Plate(20);

        Thread t1 = new Thread(new Consumer(plate));
        t1.setName("CousumerThread-" + 1);
        Thread t3 = new Thread(new Consumer(plate));
        t3.setName("CousumerThread-" + 3);
        Thread t2 = new Thread(new Producer(plate));
        t2.setName("ProducerThread-" + 2);
        Thread t4 = new Thread(new Producer(plate));
        t4.setName("ProducerThread-" + 4);
        t1.start();
        t2.start();
        t3.start();


    }

}

事实上,使用多生产者,多消费者,上述的代码是有问题的,上述中有两个消费者,都调用wait方法等待,假如生产者唤醒t1消费者,t1消费者消费一个物品后plante,调用notify唤醒,由于notify唤醒是随机的,假设此时唤醒的是消费者t3,想像一下会发生什么,此时t3线程继续往下运行!!,而plante为空,t3继续走将发生不可预知的错误,其主要原因就是因为t3在唤醒后不具备继续往后走的条件,应该继续判断是否应该往下执行,故应该将if判断该为while判断改进代码如下

package Inter;

/** 生产者
 * Created by lin on 2018/9/15.
 */
public class Consumer implements Runnable {

    private Plate plate;

    public Consumer(Plate plate) {
        this.plate = plate;
    }

    @Override
    public void run() {

            synchronized (plate) {

                //当容量小于0时,进入等待队列
                while (plate.isEmpty()) {

                    try {
                        plate.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }

                plate.getEgg();
                plate.notify();
        }
    }
}
package Inter;

/** 消费者
 * Created by lin on 2018/9/15.
 */
public class Producer implements Runnable {
    private Plate plate;

    public Producer(Plate plate) {

        this.plate = plate;

    }


    @Override
    public void run() {

            synchronized (plate) {

                while (plate.isFull()) {

                    try {
                        plate.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                Object egg = new Object();
                plate.addEgg(egg);
                plate.notify();

            }
        }
    }

话外音:在判断线程的等待条件时,应该选用while包裹,而不是if包裹

初看一下上面的代码貌似能够很好的执行,应该没问题,其实还是有问题的,问题就出在notify,

package Inter;

/**
 * Created by lin on 2018/9/15.
 */
public class Tester {

    /**
     * 当容量满的时候调用wait方法进入等待队列
     * 当竞争锁失败进入同步队列
     *
     * 假设生产者调用wait方法进入等待队列,唤醒等待队列中的消费者
     * 消费过后,plante为空,调用wait方法唤醒等待队列中的消费者,消费
     * 者判断plante为空,调用wait方法,进入等待状态,从而造成线程
     * 无限等待,没有线程会去通知其他线程苏醒,所有线程全部进入的等待区
     *
     * @param args
     */
    public static void main(String[] args) {
        Plate plate = new Plate(20);

        Thread t1 = new Thread(new Consumer(plate));
        t1.setName("CousumerThread-" + 1);
        Thread t3 = new Thread(new Consumer(plate));
        t3.setName("CousumerThread-" + 3);
        Thread t2 = new Thread(new Producer(plate));
        t2.setName("ProducerThread-" + 2);
        Thread t4 = new Thread(new Producer(plate));
        t4.setName("ProducerThread-" + 4);
        t1.start();
        t2.start();
        t3.start();
        t4.start();


    }

}

造成上面的根本原因就是因为调用notify()方法只会随机选择在等待队列中的线程随机选择一个进入同步区,如果唤醒的线程再次进入等待区,就导致所有线程都在等待区从而不能苏醒,正确的做法是,使用notifyAll()代替notify(),将等待区的所有线程都进入同步区,从而避免上述场景发生,虽然使用notifyAll()会唤醒不必要的生产者,消耗性能,但这是非常值得的。

话外音:在没有确定的把握时,请不要用notify()代替notifyAll(),即使notifyAll(),虽然notifyAll()会做一些无畏的唤醒操作,但能保证程序的正确运行。

上面已近虽然解决的多生产者,多消费者的问题,但是在JDK1.5后出现了ReentantLock锁,使用其conditon能够唤醒一类线程

从而解决notifyAll()的问题。为了更好的区分取出来的类,我们额外添加一个egg类,给每个egg类唯一标识,示例代码如下:

//新添加的egg类
package Inter.lock;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by lin on 2018/9/16.
 */
public class Egg {
    private final long id;
    private static AtomicLong sequence = new AtomicLong(0);

    public Egg() {
        this.id = sequence.getAndIncrement();
    }

    @Override
    public String toString() {
        return "egg" + this.id;
    }
}

//共享区

package Inter.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by lin on 2018/9/16.
 */
public class PlateLock {

    private List<Egg> eggs = new ArrayList<>();
    private int capacity;

    private Lock lock = new ReentrantLock();
    //等待条件,当队列为空
    private Condition empty = lock.newCondition();
    //conditon,条件,当队里满了
    private Condition Full = lock.newCondition();

    public PlateLock(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("容量不能小于0");
        }
        this.capacity = capacity;
    }

    public Object getEgg() {
        lock.lock();
        Object egg = null;
        try {
            //队里为空,等待
            while (isEmpty()) {
                empty.await();
            }


            egg = eggs.get(0);
            System.out.println("消费者取得蛋"+egg);
            eggs.remove(0);
            Full.signalAll();//此时队列不满,进行唤醒操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return egg;


    }

    public void addEgg() {

        lock.lock();
        try {
            //队里已满
            while (isFull()) {
                Full.await(); //等待
            }

            Egg egg = new Egg();
            eggs.add(egg);
            System.out.println("生产者生产蛋" + egg);
            empty.signalAll();//生产鸡蛋后,此时队里不为空,进行唤醒
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    public int getEggNum() {

        return eggs.size();
    }

    public boolean isFull() {
        return this.eggs.size() >= this.capacity;
    }

    public boolean isEmpty() {
        return this.eggs.isEmpty();
    }

}

//消费者

package Inter.lock;

import Inter.Plate;

/**
 * Created by lin on 2018/9/16.
 */
public class ConsumerLock implements Runnable {

    private PlateLock plateLock;

    public ConsumerLock(PlateLock plateLock) {
        this.plateLock = plateLock;
    }

    @Override
    public void run() {
        while (true) {
            plateLock.getEgg();
        }
    }
}

//生产者

package Inter.lock;

/**
 * Created by lin on 2018/9/16.
 */
public class ProducerLock implements Runnable {
    private PlateLock plateLock;

    public ProducerLock(PlateLock plateLock) {
        this.plateLock = plateLock;
    }

    @Override
    public void run() {
        while (true) {
            plateLock.addEgg();
        }
    }
}

 

//测试类如下

package Inter.lock;

import Inter.Consumer;
import Inter.Plate;
import Inter.Producer;

/**
 * Created by lin on 2018/9/15.
 */
public class TesterLock {


    public static void main(String[] args) {
        PlateLock plateLock = new PlateLock(20);

        Thread t1 = new Thread(new ConsumerLock(plateLock));
        t1.setName("CousumerThread-" + 1);
        Thread t3 = new Thread(new ConsumerLock(plateLock));
        t3.setName("CousumerThread-" + 3);
        Thread t2 = new Thread(new ProducerLock(plateLock));
        t2.setName("ProducerThread-" + 2);
        Thread t4 = new Thread(new ProducerLock(plateLock));
        t4.setName("ProducerThread-" + 4);
        t1.start();
        t2.start();
        t3.start();


    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值