假设在一个饭店中有一个厨师(生产者)、一个服务员(消费者),还有食物三类。
为了更明显的看到生产者消费者两个线程合作时产生的问题,先在在设置食物属性中间使其休眠。
//食物
static class Food{
private String name;
private String taste;
//为了更明显的看到生产者消费者不加休眠产生的问题,在设置中间使其休眠
public void setNameAndTaste(String name, String taste){
this.name = name;
//
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
public void get(){
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
}
}
接下来厨师和服务生分别循环生产/取走100份饭
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();//厨师线程启动
new Waiter(f).start();//服务生线程启动
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
//厨师循环生成100份饭
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
//为了更合理(厨师生产一份饭需要100ms,那服务生就休眠100ms)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
结果:
我们发现在厨师设置食物的名称和味道的时候已经发生了错乱。
原因是当我们的食物在设置这两个属性之间休眠的100ms过程中,服务员线程调用了get方法取走了饭。相当于只改了名字还没有改味道的时候就被get走了。
这就是多个线程进行协同合作时不协调的问题。
为了解决这个问题大家可能想的是直接加上synchronized同步锁就可以了,厨师做饭时服务员不能取,服务员取时厨师不能做:
//食物
static class Food{
private String name;
private String taste;
//为了更明显的看到生产者消费者不加休眠产生的问题,在设置中间使其休眠
public synchronized void setNameAndTaste(String name, String taste){
this.name = name;
//
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
public synchronized void get(){
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
}
}
但是synchronized是非公平锁,极有可能厨师做饭的锁解开之后马上继续做饭,而服务员一直排队无法取饭,或者厨师没有开始做饭,服务员一直取饭。
为了解决这个问题,就需要对厨师和服务员休眠——厨师做完就休眠,服务员端走;服务员取完就休眠,厨师做饭。
首先在食物这里设置一个属性标记是厨师还是服务员在工作,然后工作完之后更改标记的属性,唤醒其他线程并休眠自身。
//食物
static class Food{
private String name;
private String taste;
private boolean flag = true;//true表示厨师生产
//为了更明显的看到生产者消费者不加休眠产生的问题,在设置中间使其休眠
public synchronized void setNameAndTaste(String name, String taste){
if(flag){
this.name = name;
//
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag){
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果没有发生错乱并且交替执行: