目录
1、问题描述
我们学习《操作系统》的时候,学习过一个重要的概念-“生产者与消费者问题”,意思是说,对于一个固定容量的缓冲区,生产者生产数据放在该缓冲区,消费者从该缓冲区消费数据。但是,缓冲区是固定大小,生产者生产多了放不下,库存消费完了消费者也不能继续再消费。
所以,核心是要实现当缓冲区已满,生产者不能再生产;当缓冲区已空,消费者不能再消费。生产者和消费者这些线程要能了解到当前缓冲区剩余的容量。
2、问题解决
问题再具体一点:
写一个固定容量的同步容器,拥有put和get、getCount方法,能够支持2个生产者和10个消费者的阻塞调用
(1)使用wait-notifyAll控制线程的阻塞和唤醒
package interview.question2;
import java.util.LinkedList;
public class Container1<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX=10;
public synchronized void put(T o){
while (lists.size()==MAX){
try {
System.out.println("生产完毕,生产者 "+Thread.currentThread().getName()+" 阻塞中");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lists.add(o);
this.notifyAll();
}
public synchronized T get(){
while (lists.size()==0){
try {
System.out.println("库存为0,消费者 "+Thread.currentThread().getName()+" 阻塞中");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t=lists.removeFirst();
this.notifyAll();
return t;
}
public synchronized int getCount(){
return lists.size();
}
public static void main(String[] args) {
Container1 c = new Container1();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
for (int j = 0; j < 5; j++) {//每个消费者线程消耗5个对象
System.out.println(Thread.currentThread().getName()+" 消费了 "+c.get()+" 库存剩余:"+c.getCount());
}
},"consumer"+i).start();
}
for (int i = 0; i <2 ; i++) {
new Thread(()->{
for (int j = 0; j <25 ; j++) {//每个生产者线程生产25个对象
c.put(Thread.currentThread().getName()+" "+j);
System.out.println(Thread.currentThread().getName()+" 生产了 "+Thread.currentThread().getName()+" "+j+" 库存剩余:"+c.getCount());
}
},"producer"+i).start();
}
}
}
上面的代码存在一个问题,就是,notifyAll唤醒的是所有线程,包括生产者和消费者的线程,无法进行具体区分只唤醒消费者线程或者生产者线程。
所以可以考虑用Reentranlock的Condition。
(2)Condition的特点与应用
在用Condition解决该问题的过程中,写出以下代码。但是该代码有问题,最终会导致消费者线程和生产者线程都处于阻塞状态。
package interview.question2;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Condition的好处在于可以把生产线程和消费线程区分开进行唤醒
* @param <T>
*/
public class Container2<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX = 10;
ReentrantLock lock = new ReentrantLock();
Condition consumer = lock.newCondition();
Condition producer = lock.newCondition();
public void put(T o) {
try {
lock.lock();
while (lists.size() == MAX) {
System.out.println("生产完毕,生产者 "+Thread.currentThread().getName()+" 阻塞中");
producer.await();
consumer.signalAll();
}
lists.add(o);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get() {
T t=null;
lock.lock();
try {
while (lists.size() == 0) {
System.out.println("库存为0,消费者 "+Thread.currentThread().getName()+" 阻塞中");
consumer.await();
producer.signalAll();
}
t = lists.removeFirst();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public synchronized int getCount() {
return lists.size();
}
public static void main(String[] args) throws InterruptedException {
Container2 c = new Container2();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {//每个消费者线程消耗5个对象
System.out.println(Thread.currentThread().getName()+" 消费了 "+c.get()+" 库存剩余:"+c.getCount());
}
}, "consumer" + i).start();
}
TimeUnit.SECONDS.sleep(5);
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) {//每个生产者线程生产25个对象
c.put(Thread.currentThread().getName() + " " + j);
System.out.println(Thread.currentThread().getName()+" 生产了 "+Thread.currentThread().getName()+" "+j+" 库存剩余:"+c.getCount());
}
}, "producer" + i).start();
}
}
}
原因在于Condition的功能特点:
我们可以在Condition的源码中看到对await()方法的描述:
Causes the current thread to wait until it is signalled or
* {@linkplain Thread#interrupt interrupted}.
*
* <p>The lock associated with this {@code Condition} is atomically
* released and the current thread becomes disabled for thread scheduling
* purposes and lies dormant until <em>one</em> of four things happens:
* <ul>
* <li>Some other thread invokes the {@link #signal} method for this
* {@code Condition} and the current thread happens to be chosen as the
* thread to be awakened; or
* <li>Some other thread invokes the {@link #signalAll} method for this
* {@code Condition}; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the
* current thread, and interruption of thread suspension is supported; or
* <li>A "<em>spurious wakeup</em>" occurs.
* </ul>
*
意思是说:
执行await()方法后,会使得当前线程阻塞,直到signal或者Thread.interrupt()执行才会使得当前线程被唤醒。
执行await()方法后,与当前Condition关联的Lock对象自动释放。
所以,生产者和消费者线程都处于阻塞状态并不是因为死锁,而是因为在执行await()方法后,当前线程让出锁并处于阻塞状态,下面的signalAll()方法就不会被执行到了。所以最终消费者和生产者线程都是处在调用await()方法后但没有线程去主动调用signalAll()去唤醒他们。
进一步的优化如下:
package interview.question2;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Condition的好处在于可以把生产线程和消费线程区分开进行唤醒
* @param <T>
*/
public class Container2<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX = 10;
ReentrantLock lock = new ReentrantLock();
Condition consumer = lock.newCondition();
Condition producer = lock.newCondition();
public void put(T o) {
try {
lock.lock();
while (lists.size() == MAX) {
System.out.println("生产完毕,生产者 "+Thread.currentThread().getName()+" 阻塞中");
producer.await();
}
lists.add(o);
consumer.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get() {
T t=null;
lock.lock();
try {
while (lists.size() == 0) {
System.out.println("库存为0,消费者 "+Thread.currentThread().getName()+" 阻塞中");
consumer.await();
}
t = lists.removeFirst();
producer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public synchronized int getCount() {
return lists.size();
}
public static void main(String[] args) throws InterruptedException {
Container2 c = new Container2();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {//每个消费者线程消耗5个对象
System.out.println(Thread.currentThread().getName()+" 消费了 "+c.get()+" 库存剩余:"+c.getCount());
}
}, "consumer" + i).start();
}
TimeUnit.SECONDS.sleep(5);
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) {//每个生产者线程生产25个对象
c.put(Thread.currentThread().getName() + " " + j);
System.out.println(Thread.currentThread().getName()+" 生产了 "+Thread.currentThread().getName()+" "+j+" 库存剩余:"+c.getCount());
}
}, "producer" + i).start();
}
}
}