线程安全的生产者消费者四种实现方法

本文介绍了在IT面试中常见的生产者消费者问题,详细阐述了使用信号量、wait & notify机制、管道和阻塞队列四种Java实现方式,强调线程安全和互斥访问共享缓冲区的重要性。

问题描述

在IT技术面试过程中,我们经常会遇到生产者消费者问题(Producer-consumer problem), 这是多线程并发协作问题的经典案例。场景中包含三个对象,生产者(Producer),消费者(Consumer)以及一个固定大小的缓冲区(Buffer)。生产者的主要作用是不断生成数据放到缓冲区,消费者则从缓冲区不断消耗数据。该问题的关键是如何线程安全的操作共享数据块,保证生产者线程和消费者线程可以正确的更新数据块,主要考虑 1. 生产者不会在缓冲区满时加入数据. 2. 消费者应当停止在缓冲区时消耗数据. 3. 在同一时间应当只允许一个生产者或者消费者访问共享缓冲区(这一点是对于互斥操作访问共享区块的要求)。

解决方案

解决问题以上问题通常有信号量,wait & notify, 管道或者阻塞队列等几种思路。本文以Java语言为例一一进行举例讲解。

信号量

信号量(Semaphore)也称信号灯,是用来控制资源被同时访问的个数,比如控制访问数据库最大连接数的数量,线程通过acquire()获得连接许可,完成数据操作后,通过release()释放许可。对于生产者消费者问题来说,为了满足线程安全操作的要求,同一时间我们只允许一个线程访问共享数据区,因此需要一个大小为1的信号量mutex来控制互斥操作。注意到我们还定义了notFull 和 notEmpty 信号量,notFull用于标识当前可用区块的空间大小,当notFull size 大于0时表明"not full", producer 可以继续生产,等于0时表示空间已满,无法继续生产;同样,对于notEmpty信号量来说,大于0时表明 "not empty", consumer可以继续消耗,等于0 时表明没有产品,无法继续消耗。notFull初始size 为5 (5个available空间可供生产),notEmpty初始为0(没有产品可供消耗)。

   /*** 
     数据仓储class,所有的producer和consumer共享这个class对象
   **/
    static class DataWareHouse {
       //共享数据区
        private final Queue<String> data = new LinkedList();
        //非满锁
        private final Semaphore notFull;
        //非空锁
        private final Semaphore notEmpty;
        //互斥锁
        private final Semaphore mutex;

        public DataWareHouse(int capacity) {
            this.notFull = new Semaphore(capacity);
            this.notEmpty = new Semaphore(0);
            mutex = new Semaphore(1);
        }
        public void offer(String x) throws InterruptedException {
            notFull.acquire(); //producer获取信号,notFull信号量减一
            mutex.acquire(); //当前进程获得信号,mutex信号量减1,其他线程被阻塞操作共享区块data
            data.add(x);
            mutex.release(); //mutex信号量+1, 其他线程可以继续信号操作共享区块data
            notEmpty.release(); //成功生产数据,notEmpty信号量加1
        }
        public String poll() throws InterruptedException {
            notEmpty.acquire(); //notEmpty信号减一
            mutex.acquire(
在Linux系统环境下,我们可以使用Python的`threading`模块来实现一个简单的生产者-消费者(Producer-Consumer)问题的多线程示例。这里我们假设有一个共享的队列(`queue.Queue`),生产者添加产品到队列消费者队列中取出并处理产品。 ```python import threading import queue # 定义生产者类 class Producer(threading.Thread): def __init__(self, product_queue): threading.Thread.__init__(self) self.product_queue = product_queue def run(self): products = ['Product ' + str(i) for i in range(5)] for product in products: print(f"Producer is producing {product}") self.product_queue.put(product) # 模拟生产时间 time.sleep(1) # 定义消费者类 class Consumer(threading.Thread): def __init__(self, product_queue): threading.Thread.__init__(self) self.product_queue = product_queue def run(self): while True: product = self.product_queue.get() if product is None: # 消费者检查队列是否为空 break print(f"Consumer is consuming {product}") self.product_queue.task_done() # 创建队列 product_queue = queue.Queue() # 创建生产者消费者线程 producer_thread = Producer(product_queue) consumer_thread = Consumer(product_queue) # 启动线程 producer_thread.start() consumer_thread.start() # 等待所有任务完成 producer_thread.join() print("Producer finished") product_queue.put(None) # 告诉消费者生产已完成,可以退出 consumer_thread.join() print("Consumer finished") ``` 在这个例子中,我们创建了两个线程:一个生产者不断生成产品并放入队列一个消费者队列中取出产品进行处理。当生产者将None放入队列时,消费者会停止运行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值