JAVA 线程间通信(生产者/消费者模式) 作者:马育民 • 2020-01-29 15:45 • 阅读:10146 # 概述 在现实中,需要线程之间的协作。 比如说最经典的 **生产者-消费者** 模型 # 生产者-消费者 [![](https://www.malaoshi.top/upload/pic/java/1591469333,1272741648.jpg)](https://www.malaoshi.top/upload/pic/java/1591469333,1272741648.jpg) ### 生产者 负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。 ### 消费者 负责处理数据的模块(这里模块可能是:方法、对象、线程、进程 ### 缓冲区(队列) 消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。 [![](https://www.malaoshi.top/upload/0/0/1EF4t0Ug9K7W.png)](https://www.malaoshi.top/upload/0/0/1EF4t0Ug9K7W.png) ### 缓冲区的好处 1. 实现线程的并发协作 有了缓冲区以后,生产者线程只需要往缓冲区里面放数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。 2. 解耦了生产者和消费者 生产者不需要和消费者直接打交道。 3. 解决忙闲不均,提高效率 生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。 ### 注意 当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。 因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。 # 线程通信协作的两种方式: 1. syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll() 2. ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll() # 相关的类、方法 [Queue](https://www.malaoshi.top/show_1EF4sLRYsujv.html "Queue") [JAVA wait()、notify()、notifyAll()](https://www.malaoshi.top/show_1EF4t16wH42H.html "JAVA wait()、notify()、notifyAll()") # 案例 **注意:** 队列上限、休眠时间,不要乱改,否则可能无法显示队列满和空的效果 定义生产者,生产馒头 ``` import java.util.Queue; public class Productor implements Runnable { int index=1; Queue buffer=null; public Productor(Queue buffer){ this.buffer=buffer; } @Override public void run() { while (true) { synchronized (buffer) { if (buffer.size() == Main.BUFFER_MAX) { try { System.out.println("筐里的馒头数量为:"+buffer.size()+",达到上限,生产线程等待"); buffer.wait();// 让当前线程处于等待 } catch (InterruptedException e) { e.printStackTrace(); } } String str = "馒头-编号:" + index; // 馒头有编号,是为了方便看数据 buffer.offer(str);// 往框里放馒头 System.out.println("生产一个 " + str+",筐里馒头数量:"+buffer.size()); index++; // 馒头编号加1 buffer.notify(); // 通知其他线程(消费者线程)可以执行了,即:框里有馒头了,通知学生来买馒头 // 模拟生产馒头,生产馒头慢,暂停200毫秒 try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } } } } } ``` 定义消费者,卖馒头 ``` import java.util.Queue; public class Customer implements Runnable { int index=0; Queue buffer=null; public Customer(Queue buffer){ this.buffer=buffer; } @Override public void run() { while (true) { synchronized (buffer) { if (buffer.size() == 0) { // 框里没有馒头,暂停购买 try { System.out.println("筐里的馒头数量为:0,购买线程等待"); buffer.wait(); // 停止当前线程 } catch (InterruptedException e) { e.printStackTrace(); } } // poll() 返回第一个元素(第一个馒头) System.out.println("买一个 " + buffer.poll()+",筐里馒头数量:"+buffer.size()); buffer.notify();// 买了一个馒头,框里就少了一个馒头,所以通知生产者可以生成馒头了 // 模拟买馒头,买馒头快,暂停20毫秒 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } ``` 测试类 ``` import java.util.LinkedList; import java.util.Queue; public class Main { /** * 数字不能太大,否则筐里永远装不满 */ final static int BUFFER_MAX=10; public static void main(String[] args) { Queue buffer=new LinkedList(); Productor p=new Productor(buffer); Customer c=new Customer(buffer); Thread tp=new Thread(p); Thread tc=new Thread(c); tc.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } tp.start(); } } ``` 执行结果: ``` 筐里的馒头数量为:0,购买线程等待 生产一个 馒头-编号:1,筐里馒头数量:1 生产一个 馒头-编号:2,筐里馒头数量:2 生产一个 馒头-编号:3,筐里馒头数量:3 生产一个 馒头-编号:4,筐里馒头数量:4 生产一个 馒头-编号:5,筐里馒头数量:5 生产一个 馒头-编号:6,筐里馒头数量:6 生产一个 馒头-编号:7,筐里馒头数量:7 生产一个 馒头-编号:8,筐里馒头数量:8 生产一个 馒头-编号:9,筐里馒头数量:9 生产一个 馒头-编号:10,筐里馒头数量:10 筐里的馒头数量为:10,达到上限,生产线程等待 买一个 馒头-编号:1,筐里馒头数量:9 买一个 馒头-编号:2,筐里馒头数量:8 买一个 馒头-编号:3,筐里馒头数量:7 ``` 原文出处:http://malaoshi.top/show_1EF4t2GVjFe5.html