生产者消费者问题是多线程的经典问题,它描述有一个缓冲区作为仓库,生产者可以将产品放入仓库,消费者可以从仓库中取出商品。解决此类问题可以有两类方法:(1)采用某种机制保护生产者与消费者之间的同步。(2)在生产者与消费者之间建立管道
主要有四种方法:
(1)wait()/notify()方法
(2)await()/signal()方法
(3)BlockingQueue阻塞方法
(4)PipedInputStream/PipedOutputStream
wait()/notify()方法
这两个方法是Object的方法,这意味者所有的对象都有这两个方法,可以对任何对象实现同步。
wait()方法:当缓冲区已满/已空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他线程发出可执行的通知,同时放弃锁,使自己处理等待状态。
代码实现:
package produce;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
// 仓库
class Storage {
// 仓库容量
private final int MAX_SZIE = 100;
// 商品存储队列载体
private Queue<Object> list = new LinkedList<Object>();
// 生产num个产品
public void produce(int num) {
// 同步代码段
synchronized (list) {
// System.out.println("i has be notify");
while (list.size() + num > MAX_SZIE) { // 生产过剩
System.out.println("[线程名]:" + Thread.currentThread().getName() + "\t[要生产的数量]:" + num + "\t[库存量]:"
+ list.size() + "\t暂时不能执行任务");
try {
list.wait(); // 等待条件满足
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < num; i++) {
list.offer(new Object());
}
System.out.println(
"[线程名]:" + Thread.currentThread().getName() + "\t[已生产产品数]:" + num + "\t[当前库存]:" + list.size());
list.notifyAll(); // 唤醒等待线程
}
}
// 消费num个产品
public void consume(int num) {
synchronized (list) {
while (list.size() < num) {
System.out.println("[线程名]:" + Thread.currentThread().getName() + "\t[要消费的数量]:" + num + "\t[库存量]:"
+ list.size() + "\t暂时不能执行任务");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < num; i++) {
list.poll();
}
System.out.println(
"[线程名]:" + Thread.currentThread().getName() + "\t[已消费产品数]:" + num + "\t[当前库存]:" + list.size());
list.notifyAll();
}
}
}
// 生产者
class Producer extends Thread {
// 每次生产的数量
private int num;
// 所放至的仓库
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
produce();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void produce() {
storage.produce(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
class Consumer extends Thread {
// 每次消费产品数量
private int num;
// 所使用的仓库
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
consume();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void consume() {
storage.consume(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
public class WaitNotify {
public static void main(String[] args) throws InterruptedException {
Storage storage = new Storage();
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Producer p3 = new Producer(storage);
Producer p4 = 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(80);
c1.setNum(50);
c2.setNum(20);
c3.setNum(70);
p1.start();
p2.start();
p3.start();
p4.start();
c1.start();
c2.start();
c3.start();
}
}
await()/signal()方法
JDK5以后,Java提供了更健状的多线程处理机制,可以实现更细粒度的多线程控制。await()/signal()是用来做同步的两种方法,它们与最新引入的Lock直接挂勾。通过Lock对象上调用newCondition()方法,将条件变量和一个对象进行绑定,从而保证并发程序访问竞争资源的安全。
代码实现:
“`java
package produce.signal;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 仓库
class Storage {
// 仓库容量
private final int MAX_SZIE = 100;
// 商品存储队列载体
private Queue<Object> list = new LinkedList<Object>();
// 锁
private final Lock lock = new ReentrantLock();
// 仓库满时
private final Condition full = lock.newCondition();
// 仓库空时
private final Condition empty = lock.newCondition();
// 生产num个产品
public void produce(int num) {
// 同步代码段
lock.lock();
while (list.size() + num > MAX_SZIE) { // 生产过剩
System.out.println("[线程名]:" + Thread.currentThread().getName() + "\t[要生产的数量]:" + num + "\t[库存量]:"
+ list.size() + "\t暂时不能执行任务");
try {
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < num; i++) {
list.offer(new Object());
}
System.out
.println("[线程名]:" + Thread.currentThread().getName() + "\t[已生产产品数]:" + num + "\t[当前库存]:" + list.size());
// 唤醒其他线程
full.signalAll();
empty.signalAll();
// 释放锁
lock.unlock();
}
// 消费num个产品
public void consume(int num) {
lock.lock();
while (list.size() < num) {
System.out.println("[线程名]:" + Thread.currentThread().getName() + "\t[要消费的数量]:" + num + "\t[库存量]:"
+ list.size() + "\t暂时不能执行任务");
try {
// 由于库存不足,消费阻塞
empty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < num; i++) {
list.poll();
}
System.out
.println("[线程名]:" + Thread.currentThread().getName() + "\t[已消费产品数]:" + num + "\t[当前库存]:" + list.size());
// 唤醒其他线程
empty.signalAll();
full.signalAll();
// 释放锁
lock.unlock();
}
}
// 生产者
class Producer extends Thread {
// 每次生产的数量
private int num;
// 所放至的仓库
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
produce();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void produce() {
storage.produce(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
class Consumer extends Thread {
// 每次消费产品数量
private int num;
// 所使用的仓库
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
consume();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void consume() {
storage.consume(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
public class AWaitSignal {
public static void main(String[] args) throws InterruptedException {
Storage storage = new Storage();
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Producer p3 = new Producer(storage);
Producer p4 = 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(80);
c1.setNum(50);
c2.setNum(20);
c3.setNum(70);
p1.start();
p2.start();
p3.start();
p4.start();
c1.start();
c2.start();
c3.start();
}
}
“`
BlockingQueue阻塞队列
JDK5以后提供了同步队列BlockingQueue,其内部实现是上面的await()/signal()方法。阻塞操作是put()/get()方法。BlockingQueue在生成对象时就要指定大小。
put()方法:类似于上面的生产线程,达到最大容量自动阻塞。
get()方法:类似于上面的消费线程,容量为0时,自动阻塞。
“`java
package produce.blockingquue;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
// 仓库
class Storage {
// 仓库容量
private final int MAX_SZIE = 100;
// 商品存储队列载体
private BlockingQueue<Object> list = new LinkedBlockingQueue<Object>(MAX_SZIE);
// 生产num个产品
public void produce(int num) {
// 同步代码段
if (list.size() != MAX_SZIE) {
for (int i = 0; i < num; i++) {
try {
list.put(new Object());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[生产线程]:" + Thread.currentThread().getName() + "\t[当前库存:]" + list.size());
} else {
System.out.println("当前库存已满,不能再生产");
}
}
// 消费num个产品
public void consume(int num) {
if (list.size() != 0) {
for (int i = 0; i < num; i++) {
try {
list.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[生产线程]:" + Thread.currentThread().getName() + "\t[当前库存:]" + list.size());
} else {
System.out.println("当前库存为0,不能再消费");
}
}
}
// 生产者
class Producer extends Thread {
// 每次生产的数量
private int num;
// 所放至的仓库
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
produce();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void produce() {
storage.produce(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
class Consumer extends Thread {
// 每次消费产品数量
private int num;
// 所使用的仓库
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
consume();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void consume() {
storage.consume(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
public class BlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
Storage storage = new Storage();
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Producer p3 = new Producer(storage);
Producer p4 = 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(80);
c1.setNum(50);
c2.setNum(20);
c3.setNum(70);
p1.start();
p2.start();
p3.start();
p4.start();
c1.start();
c2.start();
c3.start();
}
}
#wait()/notify()的使用原则
**用法**:
如果对象调用了wait(),就会使持有当前对象锁的线程移交对象锁,然后处理阻塞状态。这个方法必须在同步区域内部被调用,否则抛异常,这个同步区域将对象锁定在了调用wait方法的对象上。下面是wait方法使用的标准模式:
```java
synchronized(obj){
while(<条件不满足>){
obj.wait(); // 释放锁
}
// 满足条件后的操作
}
应该始终使用while循环来调用wait方法,永远不要在循环之外调用wait方法。循环会在等待之前和之后测试条件是否满足,这样可以保持程序的活性和安全性。如果不在循环内调用wait方法,而简单使用if语句
可能导致其他线程调用notify/notifyAll,线程不会苏醒过来;也可能会导致,在条件不成立时,线程继续执行,破坏了被锁保护的约束关系。
notify唤醒的是单个等待的线程,notifyAll唤醒所有正在等待的线程。保守的建议是只使用notifyAll,这样能保证程序的正确性。这些线程醒过来后会检查它们正在等待的条件,如果发现条件并不满足,就会继续等待。
这里要注意几个事实:
(1)任何时刻,对象的控制权(monitor)只能被一个线程拥有。
(2)无论对象执行的是wait\notify\notifyAll方法,都必须保持当前线程取得了对象的控制权(monitor).。
(3)如果对象没有取得对象控制权就执行以上三个方法,就会报IllegalMonitorStateException异常。
(4)JVM是多线程的,不能保证运行时线程的时序性。
线程取得对象控制权的方法
(1)执行对象的某个同步实例方法。
(2)执行对象所在类的静态同步方法。
(3)执行对该对象加同步锁的同步块。
这里要特点注意,在同步块中不能更改同步对象的引用,也就是说不能重新赋值。
参考资料: