学并发编程的时候公平锁和非公平锁这两个概念我反复读了好几遍。每次看的时候好像懂了合上书又说不清楚。后来我决定自己写一遍写着写着好像真的记住了。写下来给自己看也分享给正在学的你。先说公平锁——它的问题在哪公平锁顾名思义就是先来后到。谁先来排队锁就归谁。听起来挺合理的对吧但代价藏在细节里。第一个问题是线程的挂起和唤醒很重。公平锁释放锁的时候要去唤醒排在队列最前面的那个线程。那个线程之前可能已经休眠了现在要把它叫醒。叫醒一个线程不是简单拍它一下说嘿该你干活了而是操作系统在幕后做一大堆事情从用户态切换到内核态保存当前上下文再恢复目标线程的上下文。这个过程叫上下文切换Context Switch每一次都很贵。第二个问题是CPU 缓存白费了。线程被挂起之后它之前在 CPU 缓存L1、L2、L3里存的数据大概率就失效了。等它重新被调度回来执行又得从主内存重新加载数据。就像你正在看书被人叫出去等了一个小时再回来刚才看到哪一行、讲了什么都忘了得重新翻。第三个问题更隐蔽——锁释放了但没人用。从锁释放到被唤醒的线程真正拿到 CPU 开始执行中间有一个时间差。这个时间差里锁是空着的没有人用它。等于是餐厅门口排队的顾客被叫到了但走过去还要十几秒这十几秒柜台是空的。这三个问题叠在一起结果就是公平锁的吞吐量上不去因为它把大量 CPU 时间花在了调度这件事本身而不是让线程真正干活。再说非公平锁——它为什么快非公平锁说白了就是允许插队。线程来请求锁的时候先不管队列里有谁在排队直接用 CASCompare And Swap一种轻量级的原子操作试一次——如果锁刚好是空闲的就直接拿到手了管它排不排队。这个先试一下的机制带来了四个好处。第一很多线程根本不用进入等待队列。运气好的时候锁刚释放你就到了一把就拿到了。省去了从运行到挂起、再从挂起到唤醒的完整流程。第二上下文切换的次数大幅减少。因为大量线程没有经历挂起→唤醒这个最贵的过程操作系统不需要来回切用户态和内核态了。第三CPU 缓存热度保持得好。线程没有休眠一直处于运行状态它缓存里的数据还是热的。同样是执行一段代码缓存命中和缓存失效的差别可能就是几倍甚至几十倍。第四没有调度延迟。锁释放之后下一个线程是已经活跃着的可以直接抢到锁继续跑不需要等操作系统把一个休眠的线程叫醒。锁的流转更紧凑空闲时间被压到了最低。所以选哪个公平锁和非公平锁本质上是公平和效率之间的取舍公平锁线程永远不会饿死每个线程最终都能拿到锁。代价是整体吞吐量下降。非公平锁吞吐量高但代价是可能出现极少数线程运气不好被后面的线程反复插队导致它等待的时间比理论上要长。不过在实际中非公平锁的这种饥饿概率其实很小尤其是锁竞争不是很极端的情况下基本感觉不到。所以像ReentrantLock默认就是非公平模式不是没有道理的。最后写完之后我发现所谓学懂了其实就是能用自己的话把这些东西说清楚。不用排比不用金句白描就行了。上面如果有哪里写得不准确或者啰嗦了欢迎指出来——毕竟我也是学着写的。