- 线程之间的通信问题:生产者与消费者问题
一、传统版本,用Synchronized实现的
package com.wlw.producerandconsumer;
/**
* 线程之间的通信问题:生产者与消费者问题
* 两个线程交替执行,操作同一变量,一开始为0,A B 一个执行+1 另一个执行-1
*/
public class demo01_synchronized {
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 {
//1.判断是否需要等待
if(number != 0){
this.wait();
}
//2.业务
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//3.唤醒通知 ,通知线程我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
//1.判断是否需要等待
if(number == 0){
this.wait();
}
//2.业务
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//3.唤醒通知 ,通知线程我-1完毕了
this.notifyAll();
}
}
1.1存在的问题(虚假唤醒),线程不只两个时
-
上面的案例中只有两个线程,不会出现问题,假如有四个线程就会出现问题 (虚假唤醒问题)
-
造成虚假唤醒的根本原因是notify唤醒线程和被唤醒的线程获取锁不是原子操作。在线程被唤醒过程中,如果锁被其他线程抢占执行,等持锁线程执行完后,被唤醒线程获得锁执行,就有可能造成临界资源为0的情况下被过度消费为负数的现象(在生产者消费者模式中)。
-
whlie循环在wait后再进行一次临界资源的判断,如果临界资源不满足要求继续设置线程为等待状态,避免了以上过度消费的问题出现。
-
虚假唤醒问题解决:将if 换成 while 判断 (if只会判断一次,while会一直判断)
package com.wlw.producerandconsumer;
/**
* 线程之间的通信问题:生产者与消费者问题
* 四个线程
*/
public class demo01_synchronized{
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 {
//1.判断是否需要等待
while (number != 0){
this.wait();
}
//2.业务
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//3.唤醒通知 ,通知线程我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
//1.判断是否需要等待
while (number == 0){
this.wait();
}
//2.业务
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//3.唤醒通知 ,通知线程我-1完毕了
this.notifyAll();
}
}
二、juc版(lock锁)的生产者与消费者问题
-
对比一下Synchronized,它有三个关键点:Synchronized加锁,wait等待,notify(notifyAll)唤醒,而juc版的三个关键点:lock加锁,await等待,signal(signalAll)唤醒 (jdk帮助文档中写道:在Lock取代synchronized方法和语句的使用,一个Condition取代对象 监视器的使用方法。)
-
await与,signal(signalAll)的使用要通过Condition,而Contidion是通过lock锁来获取的 (方法为:newContidion() 返回一个新的Condition实例绑定到该Lock实例。)
-
生产者与消费者 代码
package com.wlw.producerandconsumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class demo02_Lock {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"D").start();
}
}
//资源类
//写一个生产者与消费者问题,只有三步:1.判断等待、2.业务、3.唤醒通知
class Data2{
private int number = 0;
Lock lock = new ReentrantLock(); //创建锁
Condition condition = lock.newCondition();//返回一个新的Condition实例绑定到该Lock实例。
//+1
public void increment(){
lock.lock();//加锁
try {
//1.判断是否需要等待
while (number != 0){
condition.await();
}
//2.业务
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//3.唤醒通知 ,通知线程我+1完毕了
condition.signalAll(); //全部唤醒
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
//-1
public void decrement() {
lock.lock();
try {
//1.判断是否需要等待
while (number == 0){
condition.await();
}
//2.业务
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//3.唤醒通知 ,通知线程我-1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
三、Condition 条件 有什么用呢?
- 任何一个新的技术,绝对不仅仅只是覆盖了原来的技术,它一定有着自己的优势与补充
- Condition的优势:精准的通知和唤醒
package com.wlw.producerandconsumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition 实现精准的通知和唤醒线程!
* A执行完调用B B行完调用C C执行完调用A
*/
public class demo03_Condition {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; //number = 1时执行A,2B 3C
public void printA(){
lock.lock();
try {
// 判断等待,业务,唤醒
while(number != 1){
condition1.await(); //等待
}
System.out.println(Thread.currentThread().getName()+"==>AAAAAAAAAAAA"); //业务
number = 2;
// 唤醒指定的人(线程)B
condition2.signal();//唤醒
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 判断等待,业务,唤醒
while (number != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"==>BBBBBBBB"); //业务
number = 3;
// 唤醒指定的人(线程)B
condition3.signal();//唤醒
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 判断等待,业务,唤醒
while (number != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"==>CCCCCCC"); //业务
number = 1;
// 唤醒指定的人(线程)B
condition1.signal();//唤醒
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}