3、生产者和消费者问题
这是面试高频:还有其它的:单例模式、8大排序算法、死锁;
synchronized版
两个线程的情况
package com.zxh.demo01; /** * 线程之间的通信问题:也就是生产者和消费者问题! * 如何做到通信:等待唤醒,通知唤醒 * 模拟多个线程操作一个变量:对 num 进行加1、减1操作 * A num + 1 * B num - 1 */ public class A { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); } } // 如何实现通信,分三步:1、判断等待,2、业务操作,3、通知唤醒 class Data{ //资源类,数字,实现低耦合 private int number = 0; // +1 public synchronized void increment() throws InterruptedException { if(number != 0){ this.wait(); } number++; System.out.println(Thread.currentThread().getName() + " => " + number); // 通知其他线程,我执行 +1 完毕 this.notifyAll(); } // -1 public synchronized void decrement() throws InterruptedException { if(number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName() + " => " + number); // 通知其他线程,我执行 -1 完毕 this.notifyAll(); } }
顺利执行:
存在虚假唤醒问题
new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start();
问题:就是存在虚假唤醒
什么是虚假唤醒?
不需要唤醒的线程也别唤醒了,比如,B执行后,A和C都被唤醒。那么会发生什么呢?接下来看:
-
现在 A和C 进行的是 +1 操作,B和D 进行的是 -1 操作
假设程序运行的过程:
-
A 进行了 +1 操作后等待,此时进入C 等待,再进入B进行了 -1 操作后唤醒所有线程并等待
-
这个时候A 和 C 都在这个位置
如何解决呢?
在第4步的时候,C紧接着A后面被调度到,此时的number = 1,显然不能再进行 +1 操作了,但是 if 语句不能再次进行判断,所以就可以使用while语句再次进行判断,是否可以操作即可。
在jdk8的源码中,wait()方法中,就有如下说明:
修改代码,将if判断改为while循环,进行多次判断,这样就解决了虚假唤醒
/** * 线程之间的通信问题:也就是生产者和消费者问题! * 如何做到通信:等待唤醒,通知唤醒 * 模拟多个线程操作一个变量:对 num 进行加1、减1操作 * A num + 1 * B num - 1 */ public class A { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } // 如何实现通信,分三步:1、判断等待,2、业务操作,3、通知唤醒 class Data{ //资源类,数字,实现低耦合 private int number = 0; // +1 public synchronized void increment() throws InterruptedException { while(number != 0){ this.wait(); //A C } number++; System.out.println(Thread.currentThread().getName() + " => " + number); // 通知其他线程,我执行 +1 完毕 this.notifyAll(); } // -1 public synchronized void decrement() throws InterruptedException { while(number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName() + " => " + number); // 通知其他线程,我执行 -1 完毕 this.notifyAll(); } }
-
使用的是Lock锁
-
-
在源码中可以看到
-
该方法具体的说明,也可以进行等待和唤醒操作
-
点进去这个Condition接口,可以发现是Locks包下的
-
-
具体的说明如下:该接口取代了对象监视器方法的使用
-
文档里页举例该对象的使用方法,await()等待、signal()唤醒
package com.zxh.pc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 线程之间的通信问题:也就是生产者和消费者问题! * 如何做到通信:等待唤醒,通知唤醒 * 模拟多个线程操作一个变量:对 num 进行加1、减1操作 * A num + 1 * B num - 1 */ public class B { public static void main(String[] args) { Data2 data = new Data2(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } // 如何实现通信,分三步:1、判断等待,2、业务操作,3、通知唤醒 class Data2{ //资源类,数字,实现低耦合 private int number = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); //condition.await(); //等待 //condition.signalAll(); //唤醒所有 // +1 public void increment() throws InterruptedException { lock.lock(); // 加锁 try { // 业务 while(number != 0){ //等待 condition.await(); } number++; System.out.println(Thread.currentThread().getName() + " => " + number); // 通知其他线程,我执行 +1 完毕 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } // -1 public void decrement() throws InterruptedException { lock.lock(); // 加锁 try { // 业务 while(number == 0){ //等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName() + " => " + number); // 通知其他线程,我执行 -1 完毕 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } }
存在问题
任何一个新的技术,绝不仅仅是覆盖了原来的技术,肯定会有它的优势和补充!
解决问题
那么要如何解决呢?
Condition 精准通知和唤醒线程
举例
有3个线程ABC,现在想让这3个线程的执行顺序是固定的:A -> B -> C,依次循环。
-
以之前所掌握的知识,没有办法解决的。
-
JUC中 Condition 接口,相当于一个对象资源监视器,要想做到精准唤醒,使用多个监视器进行控制即可!
-
比如:在购物中,用户下单 -> 支付 -> 商品数量 - 1,每个步骤都有一个资源监视器,执行过程中,只需要唤醒对应的监视器就可以了。
测试代码
package com.zxh.pc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; // 3个线程ABC,现在想让这3个线程的执行顺序是固定的:A -> B -> C,依次循环。 public class C { public static void main(String[] args) { Data3 data = new Data3(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printA(); } }, "A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printB(); } }, "B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printC(); } }, "C").start(); } } class Data3{ // 资源类 private int number = 1; // 用于控制的变量 private Lock lock = new ReentrantLock(); //资源监视器,现有3个线程,创建3个对应的资源监视器 private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); public void printA() { lock.lock(); //加锁 try { while(number != 1){ condition1.await(); // 等待 } // 业务 number = 2; System.out.println(Thread.currentThread().getName() + "=> AAAAA"); // 唤醒,需要唤醒的是指定的线程 B 的监视器 condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } public void printB(){ lock.lock(); // 加锁 try { while(number != 2){ condition2.await(); // 等待 } // 业务 number = 3; System.out.println(Thread.currentThread().getName() + "=> BBBBBB"); //唤醒,需要唤醒指定线程 C 的监视器 condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } public void printC(){ lock.lock(); // 加锁 try { while(number != 3){ condition3.await(); // 等待 } // 业务 number = 1; System.out.println(Thread.currentThread().getName() + "=> CCCCCCC"); //唤醒,需要唤醒指定线程 A 的监视器 condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } }
成功做到精准通知