最近,我去面试了一家知名互联网大厂的高级Java开发岗。结果在面试时,面试官问了一个让我汗流浃背的问题:
“你能详细讲解一下 Java 并发中的 Condition 是如何实现等待通知机制的吗?”
我当时心里咯噔一下,这个问题不简单,毕竟平时大家用 synchronized + wait/notify 解决生产者-消费者问题挺顺手,Condition 这块用得少,没仔细研究过源码。
于是,我调整状态,整理思路,决定用直白的方式讲解 Condition 的工作原理,并分析它的底层实现。今天,我把我的思考整理出来,希望对你有所帮助!
为什么要用 Condition?ReentrantLock 和 synchronized 有什么区别?在 Java 并发编程中,我们通常有两种方式来实现线程间的同步:
方式一:使用 synchronized + wait/notify
方式二:使用 ReentrantLock + Condition
很多人都会问:“既然 wait/notify 可以实现等待通知机制,为什么还要 Condition 呢?”
这里,我们先来看 synchronized + wait/notify 存在哪些问题:
wait/notify 只能和 synchronized 搭配使用,而 synchronized 是隐式锁,不能灵活控制锁的获取和释放。
notify() 不能指定唤醒哪个线程,所有等待线程都在同一个队列里,可能导致“误唤醒”问题。
wait() 需要在 synchronized 代码块中调用,否则会抛 IllegalMonitorStateException。
synchronized 不能支持多个等待条件,notifyAll() 可能会导致不必要的唤醒,提高了系统开销。
而 ReentrantLock + Condition 能够完美地解决这些问题:
更精细的控制锁的获取与释放—— ReentrantLock 支持可重入、可中断、公平锁等特性,比 synchronized 更灵活。
支持多个 Condition—— 一个 Lock 可以创建多个 Condition,每个 Condition 维护自己的等待队列,避免无效唤醒,提高性能。
可以精准唤醒指定线程—— condition.signal() 只会唤醒特定 Condition 队列中的线程,而不是像 notifyAll() 一样一股脑全唤醒。
所以,如果你的场景需要更精细的线程等待通知控制,那 ReentrantLock + Condition 绝对是你的最佳选择!
Condition 的基本用法:生产者-消费者模型为了直观地展示 Condition 的用法,我们来看一个经典的生产者-消费者模型:
运行结果(示例):
这个例子清楚地展示了 Condition 的用法:
notFull.await() 让生产者在队列满时进入等待状态,直到消费者消费后唤醒它。
notEmpty.await() 让消费者在队列空时进入等待状态,直到生产者生产后唤醒它。
notFull.signal() 唤醒等待的生产者,notEmpty.signal() 唤醒等待的消费者。
Condition 的精准唤醒能力,比 notifyAll() 更高效!
Condition 源码解析Condition 是 Lock 绑定的条件变量,其实它的核心是AQS(AbstractQueuedSynchronizer)。
1. await() 源码解析
当线程执行 await(),它会:
释放当前持有的 Lock,让其他线程可以获取锁。
进入 Condition 的等待队列,挂起自己(阻塞)。
直到 signal() 被调用,才会重新尝试获取 Lock,然后继续执行。
2. signal() 源码解析
当某个线程调用 signal(),它会:
找到 Condition 等待队列中的第一个节点。
把这个节点移动到 AQS 的同步队列中,让它有资格去争夺锁。
当这个线程拿到锁后,就可以继续执行。
synchronized + wait/notify 是 Java 早期的等待通知机制,但不够灵活。
ReentrantLock + Condition 提供更细粒度的等待控制,可精准唤醒指定线程,提升性能。
Condition 的底层是基于 AQS 实现的,核心操作是 await() 和 signal(),配合 LockSupport 进行线程挂起和唤醒。
所以,如果你想掌握 Java 并发编程,一定要深入理解 Condition!
END你在面试中遇到过类似的问题吗?欢迎留言交流!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!