生产者和消费者模型

基于synchronized的生产者/消费者模型

其原理就是通过在获取到锁的wait/notify来实现的线程通信。

@SuppressWarnings("all")
public class ProviderConsumerWithSync {

    private Queue<Integer> queue = new LinkedList<>();

    public static void main(String[] args) {
        ProviderConsumerWithSync test = new ProviderConsumerWithSync();
        // 三个消费者 一个生产者
        for (int i =0; i < 3; i++) {
            new Consumer(test.queue, String.format("消费线程_%d",i )).start();
        }
        LockSupport.parkNanos(2000 * 1000 * 1000);
        // 生产者启动
        for (int i = 0; i < 3; i++) {
        }
        new Provider(test.queue, String.format("生产线程_%d", 1)).start();
    }

    static  class Consumer extends Thread{
        private Queue queue;

        public Consumer(Queue queue, String name) {
            super(name);
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true) { // 线程一直去工作
                synchronized (queue) { //对队列加锁
                    while (queue.size() <= 0) { // while判断而不是if判断防止虚假唤醒
                        System.out.println(Thread.currentThread().getName() + "被阻塞,等待生产");
                        try {
                            queue.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    // 当队列不为空
                    System.out.println(Thread.currentThread().getName() + "从队列中消费一个 "+ queue.remove());
                    // 唤醒生产线程进行生产
                    queue.notifyAll(); // 这里用notify去防止陷入死等待。
                }
            }
        }
    }

    static class Provider extends Thread{
        private Queue queue;

        public Provider(Queue queue, String name) {
            super(name);
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true) { // 线程一直去工作
                synchronized (queue) {
                    while (queue.size() > 10) {
                        System.out.println(Thread.currentThread().getName() + "队列达到阈值,被阻塞,等待消费");
                        // 达到上限
                        try {
                            queue.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + " 开始生产");
                    queue.add(new Integer(1));
                    LockSupport.parkNanos(2000_000_000);
                    // 去唤醒消费线程去消费
                    queue.notifyAll();
                }
            }
        }
    }
}

基于Lock和Condition的生产者/消费者模型

和synchronized一样,这里是用ReentrantLock和await/signalAll来配合使用。这里要注意是通过condition创建生产者生产和消费者消费的await条件,并且这两个条件Consumer和Provider都要持有,比如对于Provider来说,需要在生产到阈值时调用生产者Condition的await进行等待,而在生产完毕之后需要调用消费者的condition的signalAll方法来唤醒在此条件上等待的消费者。

@SuppressWarnings("all")
public class ProviderConsumerWithLock {

    private Lock lock = new ReentrantLock();
    private Queue<Integer> queue = new LinkedList<>();
    private Condition consumer = lock.newCondition();
    private Condition provider = lock.newCondition();

    public static void main(String[] args) {
        ProviderConsumerWithLock test = new ProviderConsumerWithLock();
        // 三个消费者 一个生产者
        for (int i =0; i < 3; i++) {
            new ProviderConsumerWithLock.Consumer(test, String.format("消费线程_%d",i )).start();
        }
        LockSupport.parkNanos(2000 * 1000 * 1000);
        // 生产者启动
        for (int i = 0; i < 3; i++) {
        }
        new ProviderConsumerWithLock.Provider(test, String.format("生产线程_%d", 1)).start();
    }
    static class Provider extends Thread {
        private Lock lock;
        private Queue queue;
        private Condition provider;
        private Condition consumer;
        public Provider(ProviderConsumerWithLock lock, String name) {
            super(name);
            this.lock = lock.lock;
            this.queue = lock.queue;
            this.provider = lock.provider;
            this.consumer = lock.consumer;
        }
        @Override
        public void run() {
            while (true) { // 一直工作
                lock.lock();
                try {
                    while (queue.size() > 10) { // while防止虚假唤醒
                        System.out.println(Thread.currentThread().getName() + "队列达到阈值,被阻塞,等待消费");
                        // 生产的条件await
                        provider.await();
                    }
                    System.out.println(Thread.currentThread().getName() + " 开始生产");
                    queue.add(new Integer(1));
                    Thread.sleep(500);
                    consumer.signalAll();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    static class Consumer extends Thread {
        private Lock lock;
        private Queue queue;
        private Condition provider;
        private Condition consumer;
        public Consumer(ProviderConsumerWithLock lock, String name) {
            super(name);
            this.lock = lock.lock;
            this.provider = lock.provider;
            this.consumer = lock.consumer;   this.queue = lock.queue;
        }
        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() <= 0) { // while防止虚假唤醒
                        System.out.println(Thread.currentThread().getName() + "被阻塞,等待生产");
                        // 消费的条件被阻塞
                        consumer.await();
                    }
                    // 当队列不为空
                    System.out.println(Thread.currentThread().getName() + "从队列中消费一个 "+ queue.remove());
                    // 唤醒生产线程进行生产
                    provider.signalAll();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

基于锁和线程通讯方案的两个问题

可以先看看线程通讯:

  • 线程在竞争锁失败的时候会进入Entry Set中,图中2可以表示获取锁
  • 调用线程的wait()方法让线程阻塞,线程就会放在Wait Set中(图中3即为调用wait之后释放锁)。而Wait Set中的线程可以到wait时间或者被notify之后获取锁(如图4)。
  • notify和notiifyall就是将wait set中的线程重新唤醒,放入到entry set中重新竞争锁。

虚假唤醒

即指的wait方法的调用在对 生产者阈值(比如队列满了)/消费者阈值(比如队列为空)条件判断是if的情况下,产生的虚假唤醒问题。

比如对于消费者来说:

... 省略代码
 synchronized (queue) {
        if (queue.size() <= 0) { // 这里不是while 而是if
            try {
                queue.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
              System.out.println("consumer remove: " + queue.remove() + ": " + Thread.currentThread().getName());
                        queue.notifyAll();
                        
    }
... 省略代码

如果有两个消费者线程来消费,先后都获取了锁,然后进入了wait方法等待,等再次被唤醒的时候如果没有再次判断可能会出现错误的操作(比如此时虽然被唤醒,但队列还是空,调用remove方法就会报错。)

所以这里要记得用while判断,防止不满足条件的虚假唤醒。

notify死锁问题

我们知道notify是随机唤醒一个线程去重新竞争锁。

考虑一种场景:队列大小是1,有一个生产者叫做p,两个消费者叫做c1、c2; c1和c2先启动,由于队列为空,所在都wait在wait set; p随后生产了一个消息调用notify,然后由于队列由于队列满了所以wait在wait set;假设p随机唤醒的是c1,那么c1消费完队列中的一个消息后会notify,notify会随机选择一个wait set中的线程,注意,此时c2和p都在wait set,假设唤醒的是c2,则c2由于检查到队列为空则wait,p则无人再唤醒了,这个时候就产生了死锁。如果是调用的notifyAll的话,则p和c2都会被唤醒,就会避免这种情况的发生。

所以synchronized这种没办法精准控制等待条件的锁,需要调用notifyAll来防止死锁问题。

基于阻塞队列实现的生产者/消费者模型

BlockingQueue内部已经实现了同步的队列,实现方式为await和signal

  • put方法 容量到达最大时,自动阻塞
  • take方法 容量为0时,自动阻塞

所以线程池也是经典通过阻塞队列实现的生产者-消费者模式。

比如put方法调用的putLast方法

 public void putLast(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        //      在其中调用了notEmpty的singal方法
            while (!linkLast(node))
                notFull.await();
        } finally {
            lock.unlock();
        }
    }
    
    private boolean linkLast(Node<E> node) {
        // assert lock.isHeldByCurrentThread();
        if (count >= capacity)
            return false;
        Node<E> l = last;
        node.prev = l;
        last = node;
        if (first == null)
            first = node;
        else
            l.next = node;
        ++count;
        notEmpty.signal();
        return true;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值