两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量+1,一个线程对该变量-1,实现交替,来10轮,变量初始值为0,以实现此问题作为引入,简化我们的理解
文章目录
- 一、两个线程synchronized写法-结果无问题
- 二(一)、四个线程synchronized写法-问题及解决办法
- 二(二)、4线程问题 解决办法1:使用while进行条件判断
- 二(三)、4线程问题 解决办法2:使用Lock和Condition实现线程间通信
一、两个线程synchronized写法-结果无问题
package com.atguigu.signcenter.thread;
/**
* 线程之间的通信-两个线程synchronized写法
* @author: jd
* @create: 2024-09-02
*/
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
class AirConditioner { // 资源类
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 1. 判断
if (number != 0) {
this.wait();
}
// 2. 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1. 判断
if (number == 0) {
this.wait();
}
// 2. 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}
}
结果:
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
二(一)、四个线程synchronized写法-问题及解决办法
换成4个线程会导致错误,虚假唤醒。
原因:在java多线程判断时,不能用if,程序出事出在了判断上面,突然有一添加的线程进到if了,突然中断了交出控制权,没有进行验证,而是直接走下去了,加了两次,甚至多次。
中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。if换成while。(也就是说在四个线程下,有可能两个increment线程都在if中wait,当其被唤醒时,不会再次判断number是否满足条件,而直接执行number++,因此会导致number大于1的情况,同理也会出现number小于0的情况)
如果使用if判断,则会导致虚假唤醒:代码及现象
package com.atguigu.signcenter.thread;
/**
* 线程之间的通信-两个、四个线程synchronized写法
* @author: jd
* @create: 2024-09-02
*/
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
class AirConditioner { // 资源类
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 1. 判断
if (number != 0) {
this.wait();
}
// 2. 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1. 判断
if (number == 0) {
this.wait();
}
// 2. 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}
}
现象
A 1
B 0
A 1
B 0
C 1
B 0
A 1
B 0
C 1
D 0
B -1
B -2
B -3
B -4
B -5
B -6
A -5
D -6
D -7
D -8
D -9
D -10
D -11
D -12
D -13
D -14
C -13
A -12
C -11
A -10
C -9
A -8
C -7
A -6
C -5
A -4
C -3
A -2
C -1
图示为什么会出现问题
在使用if判断两个线程的情况下,阻塞的线程只有两种情况,此时不会出现任何问题;
而使用if在四个线程的情况下,可能存在这种情况:
- 最开始+线程进行了增加操作NotifyAll;
- 此时+'线程抢占到执行权,进入if判断进入阻塞状态;
- +线程又抢到了执行权,同样进入if判断阻塞;
- -线程抢占执行权进行减操作,NotifyAll;
- +'线程抢占执行权,进行增加操作,NotifyAll;
- +线程抢占执行权,进行增加操作 (此时便出现了number=2的情况)
使用while就不会出现这种问题,因为在NotifyAll线程激活运行后,会进行二次判断!
二(二)、4线程问题 解决办法1:使用while进行条件判断
解决此问题:
使用while进行条件判断
- 高内聚第耦合的前提下,线程操作资源类
- 判断/干活/通知
- 多线程交互中,必须要防止多线程的虚假唤醒,也即(在多线程的判断中不许用if只能用while)
解决代码:
package com.atguigu.signcenter.thread;
/**
* 线程之间的通信-两个、四个线程synchronized写法
* @author: jd
* @create: 2024-09-02
*/
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
/*
class AirConditioner { // 资源类
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 1. 判断
if (number != 0) {
this.wait();
}
// 2. 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1. 判断
if (number == 0) {
this.wait();
}
// 2. 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}*/
class AirConditioner { // 资源类
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 1. 判断
while (number != 0) {
this.wait();
}
// 2. 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1. 判断
while (number == 0) {
this.wait();
}
// 2. 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
this.notifyAll();
}
}
正常结果:
A 1
B 0
A 1
B 0
A 1
B 0
C 1
B 0
A 1
B 0
C 1
B 0
A 1
D 0
C 1
B 0
A 1
D 0
C 1
B 0
A 1
D 0
C 1
B 0
A 1
D 0
C 1
B 0
A 1
D 0
C 1
D 0
A 1
D 0
C 1
D 0
C 1
D 0
C 1
D 0
二(三)、4线程问题 解决办法2:使用Lock和Condition实现线程间通信
通过Java8的Lock和Condition接口(await、signal、signalAll),可以替换synchronized与Object monitor方法(wait、notify、notifyAll)
这里我们还是使用3.2中的例子,4个线程,两个打印1两个打印0,让其交替打印,分别打印十次
package com.atguigu.signcenter.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: jd
* @create: 2024-09-02
*/
public class ThreadWaitNotifyDemo2 {
public static void main(String[] args) {
AirConditioner2 airConditioner = new AirConditioner2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
class AirConditioner2 { // 资源类
private int number = 0;
// 使用java8 lock 和 condition接口实现
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try {
// 1. 判断
while (number != 0) {
condition.await(); // this.wait();
}
// 2. 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
condition.signalAll(); // this.notifyAll();
}catch (Exception e) {
}finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
// 1. 判断
while (number == 0) {
condition.await(); // this.wait();
}
// 2. 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
condition.signalAll(); // this.notifyAll();
}catch (Exception e) {
}finally {
lock.unlock();
}
}
}
码字不易,请大家多多指教~