MST
星途 面试题库

面试题:Java条件变量与锁的协同优化

在高并发场景下,当使用Java的条件变量(Condition)结合锁(Lock)时,可能会出现哪些性能问题?你会如何优化?请举例说明优化前后的代码实现及性能对比分析。
21.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

可能出现的性能问题

  1. 线程上下文切换开销:过多的线程竞争锁和等待条件变量会导致频繁的线程上下文切换,消耗系统资源,降低性能。例如,大量线程等待某个条件变量满足,当条件满足时,众多等待线程被唤醒,只有一个线程能获取锁继续执行,其他线程又会进入等待,这种反复切换造成性能损耗。
  2. 虚假唤醒:虽然在Condition中虚假唤醒相对较少,但仍可能发生。虚假唤醒意味着线程在没有真正满足条件时被唤醒,导致不必要的计算和资源浪费。

优化方法

  1. 减少不必要的唤醒:尽量精确地控制唤醒条件,只唤醒真正需要的线程。可以使用Conditionawait()方法结合条件判断,避免虚假唤醒。
  2. 优化锁的粒度:使用更细粒度的锁,减少锁竞争。例如,将一个大的锁拆分成多个小锁,不同部分的操作使用不同的锁,降低线程竞争程度。

代码实现及性能对比分析

优化前代码

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BeforeOptimization {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean flag = false;

    public void producer() {
        lock.lock();
        try {
            // 模拟生产操作
            Thread.sleep(100);
            flag = true;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void consumer() {
        lock.lock();
        try {
            while (!flag) {
                condition.await();
            }
            // 模拟消费操作
            Thread.sleep(100);
            flag = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

优化后代码

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class AfterOptimization {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean flag = false;

    public void producer() {
        lock.lock();
        try {
            // 模拟生产操作
            Thread.sleep(100);
            flag = true;
            condition.signal(); // 只唤醒一个线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void consumer() {
        lock.lock();
        try {
            while (!flag) {
                condition.await();
            }
            // 模拟消费操作
            Thread.sleep(100);
            flag = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

性能对比分析

  • 优化前:使用signalAll()唤醒所有等待线程,会导致大量不必要的线程被唤醒,增加了线程上下文切换开销。如果有大量等待线程,性能会明显下降。
  • 优化后:使用signal()只唤醒一个线程,减少了不必要的线程唤醒和上下文切换,提高了性能。特别是在高并发场景下,线程数量较多时,优化效果更为显著。同时,结合更细粒度的锁优化,能进一步减少锁竞争,提升系统整体性能。实际性能提升可以通过性能测试工具(如JMH)进行量化分析,在不同的并发线程数场景下测量任务执行时间等指标来验证优化效果。