文章目录
- 前言
- wait/notify方法
- 实现生产者消费者模型
- 1. 使用Object的wait() / notify()方法
- 2. 使用Lock和Condition的await() / signal()方法
- 3. 使用BlockingQueue阻塞队列方法
前言
生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。
阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200111161641289.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjAzMDM1Nw==,size_16,color_FFFFFF,t_70)
wait/notify方法
首先,我们搞清楚Thread.sleep()方法和Object.wait()、Object.notify()方法的区别。根据这篇文章java sleep和wait的区别的疑惑?
1.sleep()
是Thread类的方法;而wait(),notify(),notifyAll()
是Object类中定义的方法;尽管这两个方法都会影响线程的执行行为,但是本质上是有区别的。
2.Thread.sleep()
不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep()不会让线程释放锁。如果能够帮助你记忆的话,可以简单认为和锁相关的方法都定义在Object类中,因此调用Thread.sleep()是不会影响锁的相关行为。
3.Thread.sleep
和Object.wait
都会暂停当前的线程,对于CPU资源来说,不管是哪种方式暂停的线程,都表示它暂时不再需要CPU的执行时间。OS会将执行时间分配给其它线程。区别是调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间。
线程状态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020011116174621.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjAzMDM1Nw==,size_16,color_FFFFFF,t_70)
Thread.sleep()
让线程从 【running】 -> 【阻塞态】 时间结束/interrupt -> 【runnable】
Object.wait()
让线程从 【running】 -> 【等待队列】notify -> 【锁池】 -> 【runnable】
实现生产者消费者模型
生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的方法可分为两类:(1)采用某种机制保护生产者和消费者之间的同步;(2)在生产者和消费者之间建立一个管道。第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。
同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。Java语言在多线程编程上实现了完全对象化,提供了对同步机制的良好支持。在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。
(1)Object的wait() / notify()方法
(2)Lock和Condition的await() / signal()方法
(3)BlockingQueue阻塞队列方法
(4)PipedInputStream / PipedOutputStream
本文只介绍最常用的前三种,第四种暂不做讨论。源代码在这里:Java实现生产者消费者模型
1. 使用Object的wait() / notify()方法
wait()/ nofity()
方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
- wait():当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
- notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
public class ProducerConsumer1
{
public static void main(String[] args)
{
Storage storage = new Storage();
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Producer p3 = new Producer(storage);
Producer p4 = new Producer(storage);
Producer p5 = new Producer(storage);
Producer p6 = new Producer(storage);
Producer p7 = new Producer(storage);
Consumer c1 = new Consumer(storage);
Consumer c2 = new Consumer(storage);
Consumer c3 = new Consumer(storage);
p1.setNum(10);
p2.setNum(10);
p3.setNum(10);
p4.setNum(10);
p5.setNum(10);
p6.setNum(10);
p7.setNum(80);
c1.setNum(50);
c2.setNum(20);
c3.setNum(30);
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
public static class Storage
{
private final int MAX_SIZE = 100;
private LinkedList<Object> list = new LinkedList<Object>();
public void produce(int num)
{
synchronized (list)
{
while (list.size() + num > MAX_SIZE)
{
System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:"
+ list.size() + "/t暂时不能执行生产任务!");
try
{
list.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
for (int i = 1; i <= num; ++i)
{
list.add(new Object());
}
System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());
list.notifyAll();
}
}
public void consume(int num)
{
synchronized (list)
{
while (list.size() < num)
{
System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:"
+ list.size() + "/t暂时不能执行生产任务!");
try
{
list.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
for (int i = 1; i <= num; ++i)
{
list.remove();
}
System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());
list.notifyAll();
}
}
public LinkedList<Object> getList()
{
return list;
}
public void setList(LinkedList<Object> list)
{
this.list = list;
}
public int getMAX_SIZE()
{
return MAX_SIZE;
}
}
public static class Producer extends Thread
{
private int num;
private Storage storage;
public Producer(Storage storage)
{
this.storage = storage;
}
public void run()
{
produce(num);
}
public void produce(int num)
{
storage.produce(num);
}
public int getNum()
{
return num;
}
public void setNum(int num)
{
this.num = num;
}
public Storage getStorage()
{
return storage;
}
public void setStorage(Storage storage)
{
this.storage = storage;
}
}
public static class Consumer extends Thread
{
private int num;
private Storage storage;
public Consumer(Storage storage)
{
this.storage = storage;
}
public void run()
{
consume(num);
}
public void consume(int num)
{
storage.consume(num);
}
public int getNum()
{
return num;
}
public void setNum(int num)
{
this.num = num;
}
public Storage getStorage()
{
return storage;
}
public void setStorage(Storage storage)
{
this.storage = storage;
}
}
}
注意要点
判断Queue大小为0或者大于等于queueSize时须使用 while (condition) {}
,不能使用 if(condition) {}
。其中 while(condition)
循环,它又被叫做“自旋锁”。自旋锁以及wait()和notify()方法在线程通信这篇文章中有更加详细的介绍。为防止该线程没有收到notify()
调用也从wait()中返回(也称作虚假唤醒),这个线程会重新去检查condition
条件以决定当前是否可以安全地继续执行还是需要重新保持等待,而不是认为线程被唤醒了就可以安全地继续执行了。
注意:通过共享成员变量来进行通信的,也就是synchronize(共享成员变量),对于虚假唤醒的while()中也是根据这个共享变量的信号值来的,这个例子的信号值是:队列是否为空
而在线程通信这篇文章中的信号量是一个值wasSignalled
结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200112104521175.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjAzMDM1Nw==,size_16,color_FFFFFF,t_70)
2. 使用Lock和Condition的await() / signal()方法
在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。Condition
接口的await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和Object的wait()/ nofity()
相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。下面来看代码:
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Storage
{
private final int MAX_SIZE = 100;
private LinkedList<Object> list = new LinkedList<Object>();
private final Lock lock = new ReentrantLock();
private final Condition full = lock.newCondition();
private final Condition empty = lock.newCondition();
public void produce(int num)
{
lock.lock();
while (list.size() + num > MAX_SIZE)
{
System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:" + list.size()
+ "/t暂时不能执行生产任务!");
try
{
full.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
for (int i = 1; i <= num; ++i)
{
list.add(new Object());
}
System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());
full.signalAll();
empty.signalAll();
lock.unlock();
}
public void consume(int num)
{
lock.lock();
while (list.size() < num)
{
System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:" + list.size()
+ "/t暂时不能执行生产任务!");
try
{
empty.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
for (int i = 1; i <= num; ++i)
{
list.remove();
}
System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());
full.signalAll();
empty.signalAll();
lock.unlock();
}
public int getMAX_SIZE()
{
return MAX_SIZE;
}
public LinkedList<Object> getList()
{
return list;
}
public void setList(LinkedList<Object> list)
{
this.list = list;
}
}
结果如下:
【要消费的产品数量】:50 【库存量】:0 暂时不能执行生产任务!
【要消费的产品数量】:30 【库存量】:0 暂时不能执行生产任务!
【已经生产产品数】:10 【现仓储量为】:10
【已经生产产品数】:10 【现仓储量为】:20
【要消费的产品数量】:50 【库存量】:20 暂时不能执行生产任务!
【要消费的产品数量】:30 【库存量】:20 暂时不能执行生产任务!
【已经生产产品数】:10 【现仓储量为】:30
【要消费的产品数量】:50 【库存量】:30 暂时不能执行生产任务!
【已经消费产品数】:20 【现仓储量为】:10
【已经生产产品数】:10 【现仓储量为】:20
【要消费的产品数量】:30 【库存量】:20 暂时不能执行生产任务!
【已经生产产品数】:80 【现仓储量为】:100
【要生产的产品数量】:10 【库存量】:100 暂时不能执行生产任务!
【已经消费产品数】:50 【现仓储量为】:50
【已经生产产品数】:10 【现仓储量为】:60
【已经消费产品数】:30 【现仓储量为】:30
【已经生产产品数】:10 【现仓储量为】:40
3. 使用BlockingQueue阻塞队列方法
LinkedBlockingQueue 参考博文,点击这里
JDK 1.5 以后新增的 java.util.concurrent包新增了 BlockingQueue
接口。并提供了如下几种阻塞队列实现:
java.util.concurrent.ArrayBlockingQueue
java.util.concurrent.LinkedBlockingQueue
java.util.concurrent.SynchronousQueue
java.util.concurrent.PriorityBlockingQueue
实现生产者-消费者模型使用 ArrayBlockingQueue
或者 LinkedBlockingQueue
即可。
看到这里证实了它的实现方式采用的是我们第2种await()/ signal()方法。下面我们就使用它实现吧。
import java.util.concurrent.LinkedBlockingQueue;
public class Storage
{
private final int MAX_SIZE = 100;
private LinkedBlockingQueue<Object> list = new LinkedBlockingQueue<Object>(
100);
public void produce(int num)
{
if (list.size() == MAX_SIZE)
{
System.out.println("【库存量】:" + MAX_SIZE + "/t暂时不能执行生产任务!");
}
for (int i = 1; i <= num; ++i)
{
try
{
list.put(new Object());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("【现仓储量为】:" + list.size());
}
}
public void consume(int num)
{
if (list.size() == 0)
{
System.out.println("【库存量】:0/t暂时不能执行生产任务!");
}
for (int i = 1; i <= num; ++i)
{
try
{
list.take();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println("【现仓储量为】:" + list.size());
}
public LinkedBlockingQueue<Object> getList()
{
return list;
}
public void setList(LinkedBlockingQueue<Object> list)
{
this.list = list;
}
public int getMAX_SIZE()
{
return MAX_SIZE;
}
}
结果如下:
【库存量】:0 暂时不能执行生产任务!
【库存量】:0 暂时不能执行生产任务!
【现仓储量为】:1
【现仓储量为】:1
【现仓储量为】:3
【现仓储量为】:4
【现仓储量为】:5
【现仓储量为】:6
【现仓储量为】:7
【现仓储量为】:8
【现仓储量为】:9
【现仓储量为】:10
【现仓储量为】:11
【现仓储量为】:1
【现仓储量为】:2
【现仓储量为】:13
【现仓储量为】:14
【现仓储量为】:17
【现仓储量为】:19
【现仓储量为】:20
【现仓储量为】:21
【现仓储量为】:22
【现仓储量为】:23
【现仓储量为】:24
【现仓储量为】:25
【现仓储量为】:26
【现仓储量为】:12
【现仓储量为】:1
【现仓储量为】:1
【现仓储量为】:2
【现仓储量为】:3
【现仓储量为】:4
【现仓储量为】:5
【现仓储量为】:6
【现仓储量为】:7
【现仓储量为】:27
【现仓储量为】:8
【现仓储量为】:6
【现仓储量为】:18
【现仓储量为】:2
【现仓储量为】:3
【现仓储量为】:4
【现仓储量为】:5
【现仓储量为】:6
【现仓储量为】:7
【现仓储量为】:8
【现仓储量为】:9
【现仓储量为】:10
【现仓储量为】:16
【现仓储量为】:11
【现仓储量为】:12
【现仓储量为】:13
【现仓储量为】:14
【现仓储量为】:15
【现仓储量为】:1
【现仓储量为】:2
【现仓储量为】:3
【现仓储量为】:3
【现仓储量为】:15
【现仓储量为】:1
【现仓储量为】:0
【现仓储量为】:1
【现仓储量为】:1
【现仓储量为】:1
【现仓储量为】:2
【现仓储量为】:3
【现仓储量为】:4
【现仓储量为】:0
【现仓储量为】:1
【现仓储量为】:5
【现仓储量为】:6
【现仓储量为】:7
【现仓储量为】:8
【现仓储量为】:9
【现仓储量为】:10
【现仓储量为】:11
【现仓储量为】:12
【现仓储量为】:13
【现仓储量为】:14
【现仓储量为】:15
【现仓储量为】:16
【现仓储量为】:17
【现仓储量为】:1
【现仓储量为】:1
【现仓储量为】:2
【现仓储量为】:3
【现仓储量为】:4
【现仓储量为】:5
【现仓储量为】:6
【现仓储量为】:3
【现仓储量为】:3
【现仓储量为】:1
【现仓储量为】:2
【现仓储量为】:3
【现仓储量为】:4
【现仓储量为】:5
【现仓储量为】:6
【现仓储量为】:7
【现仓储量为】:8
【现仓储量为】:9
【现仓储量为】:10
【现仓储量为】:11
【现仓储量为】:12
【现仓储量为】:13
【现仓储量为】:14
【现仓储量为】:15
【现仓储量为】:16
【现仓储量为】:17
【现仓储量为】:18
【现仓储量为】:19
【现仓储量为】:6
【现仓储量为】:7
【现仓储量为】:8
【现仓储量为】:9
【现仓储量为】:10
【现仓储量为】:11
【现仓储量为】:12
【现仓储量为】:13
【现仓储量为】:14
【现仓储量为】:15
【现仓储量为】:16
【现仓储量为】:17
【现仓储量为】:18
【现仓储量为】:19
【现仓储量为】:20
【现仓储量为】:21
【现仓储量为】:22
【现仓储量为】:23
【现仓储量为】:24
【现仓储量为】:25
【现仓储量为】:26
【现仓储量为】:27
【现仓储量为】:28
【现仓储量为】:29
【现仓储量为】:30
【现仓储量为】:31
【现仓储量为】:32
【现仓储量为】:33
【现仓储量为】:34
【现仓储量为】:35
【现仓储量为】:36
【现仓储量为】:37
【现仓储量为】:38
【现仓储量为】:39
【现仓储量为】:40
参考资料
Producer-Consumer solution using threads in Java
生产者消费者问题 - 维基百科
生产者/消费者问题的多种Java实现方式
如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例
JAVA多线程之wait/notify
java sleep和wait的区别的疑惑?
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)