线程同步
线程同步协调共享状态的访问与执行顺序。互斥锁保护临界区,条件变量用于等待状态变化,信号量表示有限资源或许可数量;三者承担不同职责。
1. 面试核心
先记住这几条,再读正文- 一次业务读改写通常包含多条机器操作,单个变量访问不等于完整操作原子。
- 同一共享不变量的所有访问必须遵守一致的同步规则。
- 条件变量等待需要持锁检查条件,并在唤醒后重新检查。
2. 临界区与共享不变量
余额初始为 100,两个线程各执行一次加 10:二者都读到 100,再分别写回 110,最终只增加了一次。应将读取、计算和写入一起放进同一临界区,或使用满足该语义的原子读改写操作。
锁的范围由不变量决定。转账同时涉及两个账户,仅分别保护两个赋值不能自动保证其他读者看不到中间状态;也不能用不同的锁分别保护同一个余额。
3. 等待条件变化
下面是条件变量的伪代码,wait 原子地释放互斥锁并进入等待,返回前重新获取锁:
lock(mutex)
while queue is empty:
wait(notEmpty, mutex)
item = queue.removeFirst()
unlock(mutex)
生产者在同一互斥规则下入队,并发出通知。消费者用 while 而不是 if,是因为可能虚假唤醒,也可能唤醒后先被其他消费者取走元素。条件变量本身不保存业务条件,真正条件是队列状态。
4. 使用边界
信号量适合控制同时占用资源的数量,例如最多允许 8 个任务进入某段处理;互斥锁通常有持有者语义,不能无条件互换。
自旋等待短临界区可减少休眠唤醒成本,但持有者被调度出去或临界区很长时会空耗 CPU。持锁执行慢网络调用会放大排队,并增加死锁风险。实际语言还需满足对应内存模型,具体 Java 保证见 Java 并发分支。
5 · 面试题卡片
先想答案,再翻面题目来自公开面经。点任意一张进入翻卡,可左右切换,看完回到这里。
逐张翻看 1 张 →5.1 线程间有哪些同步方式,互斥锁和条件变量分别解决什么问题?
出现公司:字节跳动
考察重点:互斥访问、条件等待与许可数量的区别,以及唤醒后的重新检查。
参考回答
互斥锁让同一临界区互斥访问,并通过同步规则建立数据可见性;条件变量让线程在条件不满足时释放锁并等待。唤醒后需重新持锁检查条件,通知不等于已经获得队列元素。信号量适合表达多份许可,原子操作适合能明确表达的小范围状态更新。