Java多线程实现生产消费模型的5种方式

参考博客:

  1. Java实现生产消费模型的5种方式:https://blog.csdn.net/zhaohong_bo/article/details/89378488
  2. 并发工具类(三)控制并发线程数的Semaphore:http://ifeve.com/concurrency-semaphore/

@[TOC](前言) 生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

在这里插入图片描述
以下这些解法,其实本质上都是实现了一个阻塞队列。为空,则消费者阻塞,满了,则生产者阻塞。

1.使用wait()和notify()实现

这也是最简单最基础的实现,缓冲区满和为空时都调用wait()方法等待,当生产者生产了一个产品或者消费者消费了一个产品之后会唤醒所有线程。

/*
	 * 1.使用wait()和notify()实现
	 */
	public static void testProductConsumeByWaitAndNotify() {
		final int size = 10;
		final Queue<String> queue = new ArrayDeque<String>(size);
		final Object lock = new Object();

		Runnable producer = new Runnable() {

			public void run() {
				for (int i = 0; i < 30; i++) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					String msg = "消息:" + i;
					// 队列未满,一直往里放消息
					synchronized (lock) {
						while (size == queue.size()) {
							try {
								lock.wait();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						queue.offer(msg);
						lock.notifyAll();
					}

					System.out.println(msg + " 已发送");
				}
			}
		};

		Runnable consumer = new Runnable() {

			public void run() {
				while (true) {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}

					synchronized (lock) {
						while (queue.size() == 0) {
							try {
								lock.wait();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						String msg = queue.poll();
						System.out.println(msg + "已消费");
						lock.notifyAll();
					}

				}
			}
		};

		new Thread(producer).start();
		new Thread(producer).start();
		new Thread(producer).start();
		new Thread(consumer).start();
		new Thread(consumer).start();
	}

2.可重入锁ReentrantLock的实现

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,通过对lock的lock()方法和unlock()方法实现了对锁的显示控制,而synchronize()则是对锁的隐性控制。
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响,简单来说,该锁维护这一个与获取锁相关的计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,函数调用结束计数器就减1,然后锁需要被释放两次才能获得真正释放。已经获取锁的线程进入其他需要相同锁的同步代码块不会被阻塞。
ReentrantLock的Condition:

//阻塞当前线程,直到收到通知或者被中断(将当前线程加入到当前Condition对象的等待队列里)
//Block until signalled or interrupted
public final void await() throws InterruptedException;

/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
* 把在当前Condition对象的等待队列里的等待最久的线程,转移到当前Lock的等待队列里
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
*         returns {@code false}
*/
public final void signal() ;

ReentrantLock实现生产消费模型:

/*
	 * 2.可重入锁ReentrantLock的实现
	 */
	public static void testProductConsumeByLock() {
		final Lock lock = new ReentrantLock();
		final Condition empty = lock.newCondition();
		final Condition full = lock.newCondition();

		final int size = 10;
		final Queue<String> queue = new ArrayDeque<String>(size);

		Runnable producer = new Runnable() {

			public void run() {
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				for (int i = 0; i < 20; i++) {
					lock.lock();
					try {
						if (queue.size() == size) {
							try {
								full.await();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						String msg = "生产消息:" + i;
						queue.add(msg);
						System.out.println(msg);
						empty.signal();
					} finally {
						lock.unlock();
					}
				}
			}
		};

		Runnable consumer = new Runnable() {
			public void run() {
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				while (true) {
					lock.lock();
					try {
						if (queue.isEmpty()) {
							try {
								empty.await();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						} else {
							String msg = queue.remove();
							System.out.println(msg + "已消费");
							full.signal();
						}
					} finally {
						lock.unlock();
					}
				}
			}
		};

		new Thread(producer).start();
		new Thread(producer).start();
		new Thread(producer).start();

		new Thread(consumer).start();
		new Thread(consumer).start();
	}

3.阻塞队列BlockingQueue实现

BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种:

  1. 当队列满了的时候进行入队列操作
  2. 当队列空了的时候进行出队列操作

因此,当一个线程对已经满了的阻塞队列进行入队操作时会阻塞,除非有另外一个线程进行了出队操作,当一个线程对一个空的阻塞队列进行出队操作时也会阻塞,除非有另外一个线程进行了入队操作。
从上可知,阻塞队列是线程安全的。

下面是BlockingQueue接口的一些方法:

操作抛异常特定值阻塞超时
插入add(o)offer(o)put(o)offer(o, timeout, timeunit)
移除remove(o)poll(o)take(o)poll(timeout, timeunit)
检查element(o)peek(o)

这四类方法分别对应的是:

  1. ThrowsException:如果操作不能马上进行,则抛出异常
  2. SpecialValue:如果操作不能马上进行,将会返回一个特殊的值,一般是true或者false
  3. Blocks:如果操作不能马上进行,操作会被阻塞
  4. TimesOut:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值,一般是true或者false

下面来看由阻塞队列实现的生产消费模型,这里我们使用take()和put()方法,这里生产者和生产者,消费者和消费者之间不存在同步,所以会出现连续生成和连续消费的现象

/*
	 * 3.阻塞队列BlockingQueue实现
	 * 生产者消费者
	 * 使用阻塞队列实现
	 */
	public static void testProductConsumeByBlockingQueue() throws InterruptedException {

		/*
		 * 因为SynchronousQueue没有存储功能,因此put和take会一直阻塞,直到有另一个线程已经准备好参与到交付过程中。
		 * 仅当有足够多的消费者,并且总是有一个消费者准备好获取交付的工作时,才适合使用同步队列。
		 */
		//final BlockingQueue<String> queue = new SynchronousQueue<String>(true);

		// 使用有界阻塞队列
		final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);

		Runnable producer = new Runnable() {

			public void run() {
				for (int i = 0; i < 100; i++) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					String msg = "消息:" + i;
					try {
						queue.put(msg);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					System.out.println(msg + " 已发送");
				}
			}
		};

		Runnable consumer = new Runnable() {

			public void run() {
				while (true) {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					String msg = null;
					try {
						msg = queue.take();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(msg + "已消费");
				}
			}
		};

		new Thread(producer).start();
		new Thread(consumer).start();
	}

4.信号量Semaphore的实现

信号量可以控制访问相应资源的线程的数量,从而实现生产消费模型

import java.util.concurrent.Semaphore;

/**
 * @ClassName: BySemaphore
 * @Description: TODO(4.信号量Semaphore的实现(控制并发线程数的Semaphore:http://ifeve.com/concurrency-semaphore/))
 * @author root
 * @date 2020年3月30日
 */
public class BySemaphore {
	int count = 0;
	final Semaphore put = new Semaphore(5);// 初始令牌个数
	final Semaphore get = new Semaphore(0);
	final Semaphore mutex = new Semaphore(1);// 该信号量相当于锁

	public static void main(String[] args) {
		BySemaphore bySemaphore = new BySemaphore();
		new Thread(bySemaphore.new Producer()).start();
		new Thread(bySemaphore.new Consumer()).start();
		new Thread(bySemaphore.new Consumer()).start();
		new Thread(bySemaphore.new Producer()).start();
	}

	class Producer implements Runnable {
		@Override
		public void run() {
			for (int i = 0; i < 5; i++) {
				try {
					Thread.sleep(1000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				try {
					put.acquire();// 注意顺序
					mutex.acquire();
					count++;
					System.out.println("生产者" + Thread.currentThread().getName() + "已生产完成,商品数量:" + count);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					mutex.release();
					get.release();
				}

			}
		}
	}

	class Consumer implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < 5; i++) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				try {
					get.acquire();// 注意顺序
					mutex.acquire();
					count--;
					System.out.println("消费者" + Thread.currentThread().getName() + "已消费,剩余商品数量:" + count);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					mutex.release();
					put.release();
				}
			}
		}
	}
}

5.使用消息队列

这个是取巧的办法,直接使用现成的消息中间件服务(如RocketMq、RabbitMq、Kafka等),分分钟搞定。手动微笑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一个使用Java语言实现生产者和消费模型的示例: ``` import java.util.LinkedList; public class ProducerConsumerExample { public static void main(String[] args) { LinkedList<Integer> queue = new LinkedList<>(); int maxSize = 5; Producer producer = new Producer(queue, maxSize); Consumer consumer = new Consumer(queue); Thread producerThread = new Thread(producer, "Producer"); Thread consumerThread = new Thread(consumer, "Consumer"); producerThread.start(); consumerThread.start(); } } class Producer implements Runnable { private LinkedList<Integer> queue; private int maxSize; public Producer(LinkedList<Integer> queue, int maxSize) { this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == maxSize) { try { System.out.println("队列已满,生产者等待消费..."); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int number = (int) (Math.random() * 100); queue.add(number); System.out.println("生产生产: " + number); queue.notifyAll(); } } } } class Consumer implements Runnable { private LinkedList<Integer> queue; public Consumer(LinkedList<Integer> queue) { this.queue = queue; } @Override public void run() { while (true) { synchronized (queue) { while (queue.isEmpty()) { try { System.out.println("队列为空,消费者等待生产..."); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int number = queue.removeFirst(); System.out.println("消费消费: " + number); queue.notifyAll(); } } } } ``` 上述代码实现了一个简单的生产者和消费模型,其中使用了一个线程安全的`LinkedList`队列作为生产者和消费者之间的缓冲区。在`Producer`和`Consumer`类中,`run()`方法被覆盖并实现生产消费的逻辑。 在生产线程中,如果队列已满,则生产线程将进入等待状态。当队列不满时,生产线程将生成随机数并将其添加到队列中,并通过调用`notifyAll()`方法通知消费线程可以消费了。在消费线程中,如果队列为空,则消费线程将进入等待状态。当队列不为空时,消费线程将从队列中删除第一个元素,并通过调用`notifyAll()`方法通知生产线程可以继续生产。 这实现方式使用`synchronized`关键字确保在对队列进行修改时线程安全。此外,生产者和消费线程之间的通信使用了`wait()`和`notifyAll()`方法,以确保生产者和消费者之间的协调。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值