问题:一个固定容量的同步容器,有get方法和put方法,和size()方法。n个生产者不断往里面put,m个消费者不断从中get。
方式一:Object的wait和notify
public class TestPandC {
public static void main(String[] args) {
MyContainer<Object> c=new MyContainer<Object>(6);
//3个生产者,每个人生产10个
for(int i=0;i<3;i++) {
Thread t=new Thread(new Runnable() {
public void run() {
for(int j=0;j<10;j++) {
c.put("meat");
}
}
},"p"+i);
t.start();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5个消费者,每个人吃5个
for(int i=0;i<5;i++) {
Thread t=new Thread(new Runnable() {
public void run() {
for(int j=0;j<5;j++) {
c.get();
}
}
},"c"+i);
t.start();
}
}
}
class MyContainer<T>{
final private LinkedList<T> list=new LinkedList<>();
final private int column;
private int size=0;
MyContainer(int num){
column=num;
}
public synchronized void put(T t) {
while(size==column) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"生产第"+size+"个");
list.add(t);
size++;
notifyAll();//为什么用notifyAll(),不用notify()
}
public synchronized T get() {
while(size==0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//叫醒后,并且抢到锁后,从这里开始执行
System.out.println(Thread.currentThread().getName()+"开始消费第"+size+"个");
size--;
notifyAll();
return list.removeFirst();
}
public /*synchronized*/ int size() {
return size;
}
}
这种方式有两个重点:
-
为什么wait外面用while不能用if?
因为wait()是会释放锁的,那么即使后来,被消费者叫醒,它也是没有锁的,需要重新去抢锁。既然有多个生产者,多个生产者都会去抢,假设其中一个抢到了,并且把容器又做满了,那么其他生产者再抢到时,如果是if,那么它不会再去判断了,而是继续执行wait()后面的代码,这样可能就超了。所以要用while,wait醒来抢到锁之后还要再检查一遍有没有被其他被叫醒的生产者填满。
这里即使用if double check也是一样不行。因为if(size==column)那还得重新wait,醒来又是和上面一样的问题了。
wait()在99%的情况下都是和while搭配! -
为什么要用notifyAll,而不能用notify()
因为notify是随机叫醒一个线程,假如说现在容器里没有东西了,生产者生产了一个,叫醒了一个消费者,这个消费者消费了,然后又没了,去notify,但是这时叫醒的是另外一个消费者,这个消费者一看没有就会一直等,也不会再叫醒其他人了。就会陷入死循环。
尽量用notifyAll(),少用notify()(除非个别情况,如只有一个线程)
方式二:Lock和Condition的await和signal
public class TestPandC2 {
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Condition producer=lock.newCondition();
Condition consumer=lock.newCondition();
MyContainer2<Object> c=new MyContainer2<Object>(6);
//3个生产者,每个人生产10个
for(int i=0;i<3;i++) {
Thread t=new Thread(new Runnable() {
public void run() {
for(int j=0;j<10;j++) {
c.put("meat");
}
}
},"p"+i);
t.start();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//5个消费者,每个人吃5个
for(int i=0;i<5;i++) {
Thread t=new Thread(new Runnable() {
public void run() {
for(int j=0;j<5;j++) {
c.get();
}
}
},"c"+i);
t.start();
}
}
}
class MyContainer2<T>{
final private LinkedList<T> list=new LinkedList<>();
private int size=0;
final private int column;
Lock lock=new ReentrantLock();
Condition producer=lock.newCondition();
Condition consumer=lock.newCondition();
MyContainer2(int num){
column=num;
}
public void put(T t) {
lock.lock();
while(size==column) {
try {
//让当前线程去等待。通过producer 停的,到时候就要再通过producer来叫醒
producer.await();
//我的理解就是向producer这个Condition注册一下,当叫醒时,先通知producer,producer在通知它的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"生产第"+size+"个");
list.add(t);
size++;
consumer.signalAll();//可以更精确控制叫醒的线程
lock.unlock();
}
public T get() {
lock.lock();
while(size==0) {
try {
consumer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//叫醒后,并且抢到锁后,从这里开始执行
System.out.println(Thread.currentThread().getName()+"开始消费第"+size+"个");
size--;
producer.signalAll();
lock.unlock();
return list.removeFirst();
}
public /*synchronized*/ int size() {
return size;
}
}
借用别人的一张图: