MENU

JUC相关练习

July 10, 2025 • Read: 79 • Java,学习

AI 摘要

正在加载摘要...
此文章内容记录学习 JUC 时的各种问题

问题

虚假唤醒

​虚假唤醒​​是多线程编程中的一个典型问题,它是指线程在未被明确通知(如 signal()signalAll())的情况下从等待状态中唤醒。这可能导致程序在条件未满足时继续执行,从而引发逻辑错误

为什么会发生虚假唤醒?

  1. ​底层操作系统机制​
    操作系统底层的条件变量实现(如 POSIX 的 pthread_cond_wait())允许在无显式信号时唤醒线程(通常出于性能考虑)。Java 作为跨平台语言无法完全避免这一问题。
  2. ​中断和超时机制​
    当等待的线程被外部中断(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();
        }
    }
}

image.png

可以看到已经产生了虚假唤醒

解决方案

使用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();
        }
    }
}

死锁

死锁是多线程编程中的一种严重问题,指​​两个或多个线程永久阻塞​​,每个线程都在等待其他线程释放资源的状态。

死锁产生的四个必要条件

  1. ​互斥条件​​:资源只能被一个线程占用
  2. ​持有等待​​:线程持有资源同时等待其他资源
  3. ​不可剥夺​​:资源不能被强制抢占
  4. ​循环等待​​:多个线程形成资源等待环

示例

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

解决方案

四个必要条件入手
  1. 破坏请求条件
  2. 破坏不可剥夺条件
  3. 破坏互斥条件
  4. 破坏循环等待条件

练习

创建四个线程,并按照 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