生产者与消费者(Java实现固定容量的容器支持阻塞存取操作)-多线程与高并发

本文解析了生产者消费者问题在固定容量缓冲区的应用,并探讨了如何使用wait-notifyAll与ReentrantLock的Condition特性解决线程阻塞与唤醒问题。通过实例展示了使用Condition避免线程死锁,提高并发性能。
摘要由CSDN通过智能技术生成

目录

1、问题描述

2、问题解决

 (1)使用wait-notifyAll控制线程的阻塞和唤醒

(2)Condition的特点与应用


 

1、问题描述

我们学习《操作系统》的时候,学习过一个重要的概念-“生产者与消费者问题”,意思是说,对于一个固定容量的缓冲区,生产者生产数据放在该缓冲区,消费者从该缓冲区消费数据。但是,缓冲区是固定大小,生产者生产多了放不下,库存消费完了消费者也不能继续再消费。

 

所以,核心是要实现当缓冲区已满,生产者不能再生产;当缓冲区已空,消费者不能再消费。生产者和消费者这些线程要能了解到当前缓冲区剩余的容量。

 

2、问题解决

问题再具体一点:

写一个固定容量的同步容器,拥有put和get、getCount方法,能够支持2个生产者和10个消费者的阻塞调用

 (1)使用wait-notifyAll控制线程的阻塞和唤醒

package interview.question2;

import java.util.LinkedList;

public class Container1<T> {
    final private LinkedList<T> lists = new LinkedList<>();
    final private int MAX=10;
    public synchronized void put(T o){
        while (lists.size()==MAX){
            try {
                System.out.println("生产完毕,生产者 "+Thread.currentThread().getName()+" 阻塞中");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        lists.add(o);
        this.notifyAll();
    }

    public synchronized T get(){
        while (lists.size()==0){
            try {
                System.out.println("库存为0,消费者 "+Thread.currentThread().getName()+" 阻塞中");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        T t=lists.removeFirst();
        this.notifyAll();
        return t;
    }

    public synchronized int getCount(){
        return lists.size();
    }


    public static void main(String[] args) {
        Container1 c = new Container1();

        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                for (int j = 0; j < 5; j++) {//每个消费者线程消耗5个对象
                    System.out.println(Thread.currentThread().getName()+" 消费了 "+c.get()+" 库存剩余:"+c.getCount());
                }
            },"consumer"+i).start();
        }


        for (int i = 0; i <2 ; i++) {
            new Thread(()->{
                for (int j = 0; j <25 ; j++) {//每个生产者线程生产25个对象
                    c.put(Thread.currentThread().getName()+" "+j);
                    System.out.println(Thread.currentThread().getName()+" 生产了 "+Thread.currentThread().getName()+" "+j+" 库存剩余:"+c.getCount());
                }
            },"producer"+i).start();
        }
    }

}

上面的代码存在一个问题,就是,notifyAll唤醒的是所有线程,包括生产者和消费者的线程,无法进行具体区分只唤醒消费者线程或者生产者线程。

所以可以考虑用Reentranlock的Condition。

(2)Condition的特点与应用

在用Condition解决该问题的过程中,写出以下代码。但是该代码有问题,最终会导致消费者线程和生产者线程都处于阻塞状态。

package interview.question2;

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

/**
 * 使用Condition的好处在于可以把生产线程和消费线程区分开进行唤醒
 * @param <T>
 */
public class Container2<T> {
    final private LinkedList<T> lists = new LinkedList<>();
    final private int MAX = 10;
    ReentrantLock lock = new ReentrantLock();
    Condition consumer = lock.newCondition();
    Condition producer = lock.newCondition();

    public void put(T o) {
        try {
            lock.lock();
            while (lists.size() == MAX) {
                System.out.println("生产完毕,生产者 "+Thread.currentThread().getName()+" 阻塞中");
                producer.await();
                consumer.signalAll();
            }

            lists.add(o);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public T get() {
        T t=null;
        lock.lock();
        try {
            while (lists.size() == 0) {
                System.out.println("库存为0,消费者 "+Thread.currentThread().getName()+" 阻塞中");
                consumer.await();
                producer.signalAll();
            }

            t = lists.removeFirst();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
           lock.unlock();
        }
        return t;
    }

    public synchronized int getCount() {
        return lists.size();
    }


    public static void main(String[] args) throws InterruptedException {
        Container2 c = new Container2();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {//每个消费者线程消耗5个对象
                    System.out.println(Thread.currentThread().getName()+" 消费了 "+c.get()+" 库存剩余:"+c.getCount());
                }
            }, "consumer" + i).start();
        }

        TimeUnit.SECONDS.sleep(5);

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 25; j++) {//每个生产者线程生产25个对象
                    c.put(Thread.currentThread().getName() + " " + j);
                    System.out.println(Thread.currentThread().getName()+" 生产了 "+Thread.currentThread().getName()+" "+j+" 库存剩余:"+c.getCount());
                }
            }, "producer" + i).start();
        }
    }

}

原因在于Condition的功能特点:

我们可以在Condition的源码中看到对await()方法的描述:

Causes the current thread to wait until it is signalled or
     * {@linkplain Thread#interrupt interrupted}.
     *
     * <p>The lock associated with this {@code Condition} is atomically
     * released and the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until <em>one</em> of four things happens:
     * <ul>
     * <li>Some other thread invokes the {@link #signal} method for this
     * {@code Condition} and the current thread happens to be chosen as the
     * thread to be awakened; or
     * <li>Some other thread invokes the {@link #signalAll} method for this
     * {@code Condition}; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread, and interruption of thread suspension is supported; or
     * <li>A &quot;<em>spurious wakeup</em>&quot; occurs.
     * </ul>
     *

意思是说:

执行await()方法后,会使得当前线程阻塞,直到signal或者Thread.interrupt()执行才会使得当前线程被唤醒。

执行await()方法后,与当前Condition关联的Lock对象自动释放。

所以,生产者和消费者线程都处于阻塞状态并不是因为死锁,而是因为在执行await()方法后,当前线程让出锁并处于阻塞状态,下面的signalAll()方法就不会被执行到了。所以最终消费者和生产者线程都是处在调用await()方法后但没有线程去主动调用signalAll()去唤醒他们。

进一步的优化如下:

package interview.question2;

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

/**
 * 使用Condition的好处在于可以把生产线程和消费线程区分开进行唤醒
 * @param <T>
 */
public class Container2<T> {
    final private LinkedList<T> lists = new LinkedList<>();
    final private int MAX = 10;
    ReentrantLock lock = new ReentrantLock();
    Condition consumer = lock.newCondition();
    Condition producer = lock.newCondition();

    public void put(T o) {
        try {
            lock.lock();
            while (lists.size() == MAX) {
                System.out.println("生产完毕,生产者 "+Thread.currentThread().getName()+" 阻塞中");
                producer.await();
            }

            lists.add(o);
            consumer.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public T get() {
        T t=null;
        lock.lock();
        try {
            while (lists.size() == 0) {
                System.out.println("库存为0,消费者 "+Thread.currentThread().getName()+" 阻塞中");
                consumer.await();
            }

            t = lists.removeFirst();
            producer.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
           lock.unlock();
        }
        return t;
    }

    public synchronized int getCount() {
        return lists.size();
    }


    public static void main(String[] args) throws InterruptedException {
        Container2 c = new Container2();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {//每个消费者线程消耗5个对象
                    System.out.println(Thread.currentThread().getName()+" 消费了 "+c.get()+" 库存剩余:"+c.getCount());
                }
            }, "consumer" + i).start();
        }

        TimeUnit.SECONDS.sleep(5);

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 25; j++) {//每个生产者线程生产25个对象
                    c.put(Thread.currentThread().getName() + " " + j);
                    System.out.println(Thread.currentThread().getName()+" 生产了 "+Thread.currentThread().getName()+" "+j+" 库存剩余:"+c.getCount());
                }
            }, "producer" + i).start();
        }
    }

}
 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值