什么是生产者、消费者问题?
生产者、消费者问题是在多线程协作通过不同方法操作同一变量而
产生的线程安全问题。如下代码说明了生产者厨师Cook通过set方
法设置食物Food,服务生Writer通过get方法获取食物Food时产
生的线程安全问题。
Cook依次生产"咸香味"的"皮蛋瘦肉粥"和"糖醋味"的“糖醋里脊”
Writer负责将生产完成的食物产出
public class test {
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() {
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++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
static class Food{
private String name;
private String taste;
public void setNameAndSaste(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);
}
}
}

可以看到上图,当Cook将名称设置完毕后,在设置味道之前
Writer将食物端走,造成了数据错乱的问题
尝试使用线程同步机制解决(并无法解决)
为set方法和get方法加上synchronized关键字修饰,保证线程
安全
public class test {
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() {
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++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
static class Food{
private String name;
private String taste;
public synchronized void setNameAndSaste(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关键字后确实保证了
Cook在生产一份Food生产完成前Writer无法将该份Food端走,
但是Writer却出现了多次端走同一份Food的操作,即未等待
Cook生产Food,只是重复的端走,因为synchronized修饰方法
时如果是静态方法锁对象是类名.class,修饰非静态方法时锁对
象是this即当前对象,所以此时get和set只会有一个方法获得锁,
但是无法避免当锁被释放时该方法再次获得该锁.
wait()方法解决连续获取锁问题
接着同步机制后的结果进行思考,之所以出现重复端走Food的操
作原因是Writer与Cook在竞争锁时有一方连续获得了锁对象,
为了让Cook和Writer可以排队顺序进行,当Cook或Writer执行
完相应得动作后调用wait()方法让其进入等待池,不再参与锁对
象的竞争,但在wit()方法调用之前需要调用notifyAll()方法,
让该锁对象等待池中的线程退出wait状态
public class test {
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() {
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++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
static class Food {
private String name;
private String taste;
public synchronized void setNameAndSaste(String name, String taste) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void get() {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

至此,生产者与消费者问题得到解决,当生产者生产时,消费者进入
等待池中,当生产结束后唤醒等待池中的消费者并自己进入等待池,
当消费者消费结束后唤醒等待池中的生产者并自己进入等待池如此
往复,便实现了生产者和消费者排队交替协作完成任务的场景.