生产者-消费者模式实现(ReentarntLock)

生产者-消费者模式实现(ReentarntLock)

1. 介绍

在JAVA多线程编程中,生产者-消费者模式有多种实现方式,例如:

  • synchronized+wait/notify
  • 阻塞队列BolckingQueue
  • ReentrantLock+两个Condition条件

在这篇随笔中主要考虑利用ReentarntLockCondition实现, 重点是利用中断实现了生产者和消费者线程的执行终止

2. 代码实现

类文件简介:

  • Container 提供了向共享队列入队和出队的方法
  • Consumer 封装了消费者任务
  • Producer 封装生产者任务
  • MainTest 执行测试

Container类实现:

public class Container<T> {
    private final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;
    private final Queue<T> sharedQueue;
    private final int capacity;

    public Container( Queue<T> sharedQueue, int capacity) {
        if(sharedQueue == null){
            throw new NullPointerException("sharedQueue is null");
        }
        if (capacity < 1){
            throw new IllegalArgumentException("capacity less one");
        }
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.notFull = lock.newCondition();
        this.sharedQueue = sharedQueue;
        this.capacity = capacity;
    }

    /**
     * 生产者生产操作
     * @param element  生产的元素
     * @throws InterruptedException 如果当前线程被中断
     */
    public void put(T element) throws InterruptedException {
        try {
            lock.lockInterruptibly();
            while (sharedQueue.size() == capacity){
                notFull.await();
            }
            sharedQueue.offer(element);

            System.out.println("生产者线程-" + Thread.currentThread().getId()+" 生产: "+element+" 队列元素数量: " + sharedQueue.size());
            notEmpty.signal();
        }finally {
            try{
                lock.unlock();
            }catch (IllegalMonitorStateException e){}
        }
    }

    /**
     * 消费者操作
     * @return 返回获得的元素
     * @throws InterruptedException 如果当前线程被中断
     */
    public T take() throws InterruptedException {
        try {
            lock.lockInterruptibly();
            while (sharedQueue.isEmpty()){
                notEmpty.await();
            }
            T element =  sharedQueue.poll();
            System.out.println("消费者线程-" + Thread.currentThread().getId()+" 消费 "+ element +" 队列元素数量: " + sharedQueue.size());
            notFull.signal();
            return element;
        }finally{
            try{
                lock.unlock();
            }
            catch (IllegalMonitorStateException e){}
        }
    }
}

对于Container类有一下几点说明:

  • 加锁时使用lock.lockInterruptibly()而不是lock.lock(),使得在竞争锁期间可以响应中断。
  • take()put()方法向外抛出InterruptedException是为了在外部调用时跳出死循环,从而实现线程的退出。InterruptedException异常是由方法内部lockInterruptibly()await()方法抛出的。
  • finally语句块中对lock.unlock()捕获了IllegalMonitorStateException异常。这是因为当线程在调用lockInterruptibly后竞争锁失败,进入自选阻塞状态,如果在此时被中断,则在函数返回前执行lock.unlock()时没有获得锁就会产生IllegalMonitorStateException异常,所以进行了捕获。另外,当线程调用await()方法进入阻塞状态时,被中断返回时,是保证一定该线程获得锁的(),所以不会此种情况导致lock.unlock()抛出IllegalMonitorStateException异常。
  • await()方法返回线程一定获得该条件所属的锁(OpenJDK8 Condition#await() 部分注释)

    In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.

ConsumerProducer类实现

两个类实现比较类似就一起介绍

  • Consumer类:
public class Consumer implements Runnable{
    private final Container<Integer> container;
    public Consumer(Container<Integer> container) {
        this.container = container;
    }
    @Override
    public void run() {
        try{
            while (true){
                // 模拟消费耗时
                // Thread.sleep(new Random().nextInt(100)+50);
                Integer element = container.take();
                // doSomething
            }
        }// InterruptedException 异常抛出有两处, Thread.sleep()方法、container.take()方法
        catch (InterruptedException e){
            System.out.println("消费者线程-" + Thread.currentThread().getId()+" 执行完毕");
        }
    }
}

Producer类:

public class Producer implements Runnable{
    private final Container<Integer> container;
    public Producer(Container<Integer> container) {
        this.container = container;
    }
    @Override
    public void run() {
        try{
            while (true){
                Random random = new Random();
                //sleep模拟生产产品的耗时
                //  Thread.sleep(new Random().nextInt(100)+50);
                int element = random.nextInt(1000);
                container.put(element);
            }
        }// InterruptedException异常抛出有两处: Thread.sleep()方法、container.put()方法
        catch (InterruptedException e){
            System.out.println("生产者线程-" + Thread.currentThread().getId()+" 执行完毕");
        }
    }
}

测试类MainTest实现

MainTest代码:

public class MainTest {
    public static void main(String[] args){
        Container<Integer> container = new Container<>(new ArrayDeque<>(), 5);
        //为了使所有提交任务都能及时执行,使用newCachedThreadPool()方法创建线程池
        ExecutorService service = Executors.newCachedThreadPool();
        
        int consumerCount = 5; //消费者线程数
        for (int i = 0; i < consumerCount; i++) {
            service.execute(new Consumer(container));
        }

        int producerCount = 4; //生产者线程数
        for (int i = 0; i < producerCount; i++) {
            service.execute(new Producer(container));
        }
        //阻塞主线程 一段时间
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //关闭线程池
        service.shutdownNow();
        boolean isTerminated = false;
        while (!isTerminated){
            try {
                isTerminated = service.awaitTermination(100, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("主线程退出");
    }
}

MainTest中需要注意的是关闭线程池调用的函数是ExecutorService.shutdownNow()而不是ExecutorService.shutdown()。调用shutdownNow()后线程池进入STOP状态,此时会中断所以线程(包括正在执行的线程),清空线程池的工作队列;而调用shutdown()后线程池进入SHUTDOWN状态,在此状态下只会中断空闲线程,不中断正在执行的线程,所以如果调用shutdown()在该程序会一直运行,不会停下来。

3. 运行结果

  • 其中一次运行结果(部分)为:
生产者线程-19 生产: 686 队列元素数量: 2
生产者线程-19 生产: 865 队列元素数量: 3
生产者线程-19 生产: 346 队列元素数量: 4
生产者线程-19 生产: 191 队列元素数量: 5
消费者线程-13 消费 899 队列元素数量: 4
消费者线程-13 消费 686 队列元素数量: 3
消费者线程-13 消费 865 队列元素数量: 2
消费者线程-16 消费 346 队列元素数量: 1
消费者线程-16 消费 191 队列元素数量: 0
生产者线程-20 生产: 56 队列元素数量: 1
生产者线程-20 生产: 203 队列元素数量: 2
生产者线程-20 生产: 902 队列元素数量: 3
.......
.......
.......
生产者线程-17 生产: 180 队列元素数量: 1
生产者线程-17 生产: 550 队列元素数量: 2
生产者线程-17 生产: 925 队列元素数量: 3
消费者线程-13 消费 180 队列元素数量: 2
消费者线程-14 消费 550 队列元素数量: 1
生产者线程-17 执行完毕
生产者线程-18 生产: 722 队列元素数量: 2
生产者线程-20 生产: 998 队列元素数量: 3
消费者线程-12 消费 925 队列元素数量: 2
消费者线程-14 执行完毕
生产者线程-18 执行完毕
消费者线程-13 执行完毕
生产者线程-20 执行完毕
消费者线程-15 消费 722 队列元素数量: 1
消费者线程-12 执行完毕
生产者线程-19 生产: 971 队列元素数量: 2
消费者线程-16 消费 998 队列元素数量: 1
消费者线程-15 执行完毕
生产者线程-19 执行完毕
消费者线程-16 执行完毕
主线程退出

4. 补充测试

MainTest测试中使用了线程池管理线程,并中断线程。如果不使用线程池而直接使用new Thread()的方法生产线程,则可以按照如下代码MainTest2测试:

public class MainTest2 {
    public static void main(String[] args) {
        Container<Integer> container = new Container<>(new ArrayDeque<>(), 5);
        //定义消费者线程
        Runnable consumer = new Consumer(container);
        int consumerCount = 5;
        List<Thread> consumerThreads = new LinkedList<>();
        for (int i = 0; i < consumerCount; i++) {
            Thread thread = new Thread(consumer);
            consumerThreads.add(thread);
            thread.start();
        }
        //定义生产者线程
        Runnable producer= new Producer(container);
        int producerCount = 4;
        List<Thread> producerThreads = new LinkedList<>();
        for (int i = 0; i < producerCount; i++) {
            Thread thread = new Thread(producer);
            producerThreads.add(thread);
            thread.start();
        }
        //阻塞主线程 一段时间
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //通过主线程依次中断生产者和消费者线程
        for (Thread consumerThread : consumerThreads) {
            consumerThread.interrupt();
        }
        for (Thread producerThread : producerThreads) {
            producerThread.interrupt();
        }
        //等待线程全部执行结束后主线程才退出
        boolean isRunning = true;
        while (isRunning){
            isRunning = false;
            for (Thread consumerThread : consumerThreads) {
                isRunning = isRunning || consumerThread.isAlive();
            }
            for (Thread producerThread : producerThreads) {
                isRunning = isRunning || producerThread.isAlive();
            }
        }
        System.out.println("主线程退出");
    }
}

5. 参考:

  1. 经典并发同步模式:生产者-消费者设计模式

本文主要目的是记录学习过程,加深对知识点理解; 如有行文有误,望指正。

posted @ 2022-04-06 21:43  HelloSakura  阅读(198)  评论(0编辑  收藏  举报