AI 摘要
正在加载摘要...
此文章内容记录学习 JUC 时的各种问题
问题
虚假唤醒
虚假唤醒是多线程编程中的一个典型问题,它是指线程在未被明确通知(如signal()或signalAll())的情况下从等待状态中唤醒。这可能导致程序在条件未满足时继续执行,从而引发逻辑错误
为什么会发生虚假唤醒?
- 底层操作系统机制
操作系统底层的条件变量实现(如 POSIX 的pthread_cond_wait())允许在无显式信号时唤醒线程(通常出于性能考虑)。Java 作为跨平台语言无法完全避免这一问题。 - 中断和超时机制
当等待的线程被外部中断(interrupt())或达到超时时间时,也可能意外唤醒。
class SharedResource {
private int item = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 生产者方法:非安全实现
public void produce() throws InterruptedException {
lock.lock();
try {
// 错误:使用if而非while检查条件
if (item != 0) {
condition.await(); // 可能虚假唤醒后继续执行
}
item++;
System.out.println("生产: " + item);
condition.signal();
} finally {
lock.unlock();
}
}
// 消费者方法:非安全实现
public void consume() throws InterruptedException {
lock.lock();
try {
// 错误:使用if而非while检查条件
if (item == 0) {
condition.await(); // 可能虚假唤醒后继续执行
}
System.out.println("消费: " + item);
item--;
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SharedResource share = new SharedResource();
for(int i=0; i<2; i++) {
new Thread(() -> {
for (int j = 0; j < 100; j++) {
try { share.produce(); }
catch (InterruptedException e) { e.printStackTrace(); }
}
}, "Producer" + i).start();
}
for(int i=0; i<2; i++) {
new Thread(() -> {
for (int j = 0; j < 100; j++) {
try { share.consume(); }
catch (InterruptedException e) { e.printStackTrace(); }
}
}, "Consumer" + i).start();
}
}
}
可以看到已经产生了虚假唤醒
解决方案
使用while代替if
class SafeResource {
private int value = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
// 安全的生产者
public void safeIncrement() throws InterruptedException {
lock.lock();
try {
// 修复:使用while循环防御虚假唤醒
while (value != 0) {
condition.await();
}
value++;
System.out.println(Thread.currentThread().getName()
+ " 安全生产后: " + value);
condition.signalAll();
} finally {
lock.unlock();
}
}
// 安全的消费者
public void safeDecrement() throws InterruptedException {
lock.lock();
try {
// 修复:使用while循环防御虚假唤醒
while (value != 1) {
condition.await();
}
value--;
System.out.println(Thread.currentThread().getName()
+ " 安全消费后: " + value);
condition.signalAll();
} finally {
lock.unlock();
}
}
}死锁
死锁是多线程编程中的一种严重问题,指两个或多个线程永久阻塞,每个线程都在等待其他线程释放资源的状态。
死锁产生的四个必要条件
- 互斥条件:资源只能被一个线程占用
- 持有等待:线程持有资源同时等待其他资源
- 不可剥夺:资源不能被强制抢占
- 循环等待:多个线程形成资源等待环
示例
public class DeadLockExample {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(() -> {
synchronized (a) {
System.out.println("获取到了锁 a, 尝试获取锁 b");
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("获取到了锁 b");
}
}
}, "A").start();
new Thread(() -> {
synchronized (b) {
System.out.println("获取到了锁 b, 尝试获取锁 a");
synchronized (a) {
System.out.println("获取到了锁 a");
}
}
}, "B").start();
}
}获取到了锁 a, 尝试获取锁 b
获取到了锁 b, 尝试获取锁 a解决方案
从四个必要条件入手
- 破坏请求条件
- 破坏不可剥夺条件
- 破坏互斥条件
- 破坏循环等待条件
练习
创建四个线程,并按照 D -> B -> C -> A 的顺序执行
class Control {
private int flag = 0;
private final int MAX_NUMBER = 4;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void wait(int target) throws InterruptedException {
lock.lock();
try {
while(flag % MAX_NUMBER != target - 1) {
condition.await();
}
flag++;
System.out.println(Thread.currentThread().getName());
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public class Test01 {
public static void main(String[] args) {
Control control = new Control();
new Thread( () -> {
try {
control.wait(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "A" ).start();
new Thread( () -> {
try {
control.wait(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "B" ).start();
new Thread( () -> {
try {
control.wait(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "C" ).start();
new Thread( () -> {
try {
control.wait(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "D" ).start();
}
}输出
D
B
C
A 如无特殊说明 JUC相关练习 为博主 Lin 原创,转载请注明原文链接: https://blog.lin03.cn/archives/34/