Java synchroinzed和ReentrantLock
synchronized —— JVM亲儿子的暗黑兵法
核心思想:“锁即对象,对象即锁!”
底层三板斧
- 对象头里的锁密码
每个Java对象头里藏了两个骚东西:
- Mark Word:32/64位的比特修罗场,存哈希码、GC年龄,锁状态标识
- Klass Pointer:指向类的元数据(相当于对象的户口本)
当锁启用时,Mark Word变身成锁状态记录仪:
| 锁状态 | 存储内容 |
|-------------|---------------------------------|
| 无锁 | 对象哈希码 + 分代年龄 |
| 偏向锁 | 偏向线程ID + Epoch + 分代年龄 |
| 轻量级锁 | 指向栈中锁记录的指针 |
| 重量级锁 | 指向互斥量(mutex)的指针 |
- Monitor 监视器の奥义
每个对象关联一个Monitor对象(C++实现),关键字段:
- _owner:当前持有锁的线程
- _EntryList:等锁的线程排队区
- _WaitSet:调了wait()的线程停尸房
- _recursions:重入次数计数器
当线程抢锁时:
- CAS操作试图把Mark Word改成自己的栈帧指针(轻量级锁)
- 失败就膨胀为重量级锁,向操作系统申请mutex(用户态切内核态,性能血崩!)
- 锁升级の死亡行军
synchronized的终极骚操作——锁膨胀策略:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
- 偏向锁:第一个线程过来直接贴标签(Mark Word写线程ID),相当于在厕所门口挂“张老三专属”
- 轻量级锁:有竞争时升级,线程通过CAS自旋抢锁(在用户态疯狂空转,省去内核切换)
- 重量级锁:自旋超过阈值(默认10次)或竞争激烈,直接召唤操作系统级mutex(线程进内核态睡觉)
设计哲学:“能BB就别动手,能用户态解决绝不麻烦内核!”
ReentrantLock —— AQS黑社会の暴力美学
核心思想:“排队砍人,CAS冲锋!”
AQS(AbstractQueuedSynchronizer)解剖室
AQS的核心是一个volatile int state + 一个CLH队列:
- state:表示锁被重入的次数(0表示没人持有)
- CLH队列:抢锁失败的线程排成双向链表(实际上是变种的CLH)
// AQS的核弹级数据结构
public abstract class AbstractQueuedSynchronizer {volatile Node head; // 队头(注意是延迟初始化的)volatile Node tail; // 队尾volatile int state; // 锁状态// Node节点结构static final class Node {volatile Thread thread;volatile Node prev;volatile Node next;int waitStatus; // CANCELLED/SIGNAL等状态}
}
加锁の血腥流程(以非公平锁为例)
- tryLock()冲锋:
线程直接CAS抢state(不排队!),成功就设置自己为独占线程
final void lock() {if (compareAndSetState(0, 1)) // 直接插队!setExclusiveOwnerThread(Thread.currentThread());elseacquire(1); // 进AQS排队系统
}
- acquire()的阴招三连:
public final void acquire(int arg) {if (!tryAcquire(arg) && // 再试一次能不能偷鸡acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并阻塞selfInterrupt();
}
- tryAcquire():子类实现的抢锁逻辑(ReentrantLock里会判断是否可重入)
- addWaiter():用CAS把当前线程包装成Node扔到队列尾部
- acquireQueued():在队列中不断尝试抢锁,失败就调用LockSupport.park()睡觉
- 解锁の连锁反应:
释放锁时唤醒队列中的下一个节点:
public final boolean release(int arg) {if (tryRelease(arg)) { // state减到0才算真释放Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 唤醒下一个线程return true;}return false;
}
公平锁 vs 非公平锁の哲学战争
-
非公平锁(默认):
新线程可以直接插队抢锁(不管队列里有没有人)final boolean nonfairTryAcquire(int acquires) {// 不管队列直接CAS抢! }
优点:吞吐量高(减少线程切换)
缺点:可能饿死老线程 -
公平锁:
必须老老实实排队(hasQueuedPredecessors()检查队列)protected final boolean tryAcquire(int acquires) {if (getState() == 0) {if (!hasQueuedPredecessors() && // 检查有没有前辈在等compareAndSetState(0, acquires)) {setExclusiveOwnerThread(currentThread());return true;}}// ...重入逻辑 }
优点:文明礼貌
缺点:性能损失约10%-15%
灵魂暴击——设计思想の终极对决
synchronized | ReentrantLock | |
---|---|---|
锁本质 | JVM层实现的监视器锁(C++代码操控对象头) | JDK层基于AQS实现的显式锁(纯Java代码骚操作) |
锁管理 | 自动锁升级降级(偏向->轻量->重量) | 全靠代码手动控制(state+CLH队列硬刚) |
中断响应 | 不支持(等锁时无法中断) | 支持lockInterruptibly()(等锁可抛中断异常) |
条件队列 | 一个对象只有一个等待队列(wait/notify全体) | 可创建多个Condition实现精准唤醒(如生产者消费者) |
性能策略 | 乐观锁优先(CAS自旋),失败转悲观锁 | 直接硬核CAS+CLH队列,可控性更强 |
设计哲学 | “让程序员无脑用” —— JVM自动优化 | “把锁当乐高玩” —— 高度可定制化 |
源码级の高潮时刻
看synchronized的C++源码片段(hotspot源码):
// 对象头操作
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, TRAPS) {if (UseBiasedLocking) { // 偏向锁开关if (!SafepointSynchronize::is_at_safepoint()) {BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, false, THREAD);if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) return;}}slow_enter(obj, lock, THREAD); // 进慢路径(轻量级锁)
}
再看ReentrantLock的tryLock实现:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) { // CAS冲锋!setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) { // 重入判断int nextc = c + acquires;if (nextc < 0) throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}
(拍桌子)现在够不够底层?这他妈就是锁的黑暗森林法则——
synchronized玩的是空间换时间(锁升级),ReentrantLock玩的是精细控制(AQS队列)!下次面试被问锁机制,直接把这拍面试官脸上!(摔门而去声)