Java对象头的组成#
Java对象头主要是由Mark Word、Class Pointer(类指针)、array length 组成
Mark Word#
-
identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法
System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。 -
age:存储GC分代年龄
-
biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
-
lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。
-
ptr_to_lock_record:指向栈中锁记录的指针。
-
ptr_to_heavyweight_monitor:指向Monitor重量级锁监视器的指针
| biased_lock | lock | 状态 |
|---|---|---|
| 0 | 01 | 无锁 |
| 1 | 01 | 偏向锁 |
| 0 | 00 | 轻量级锁 |
| 0 | 10 | 重量级锁 |
| 0 | 11 | GC标记 |
下面是不同锁状态下的mark word信息:

Class Pointer(类指针)#
这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:
- 每个Class的属性指针(即静态变量)
- 每个对象的属性指针(即对象变量)
- 普通对象数组的每个元素指针
当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
array length#
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。
锁升级#
基于我们对mark word的了解,我们可以看到锁总共有以下几种:无锁、偏向锁、轻量级锁、重量级锁,锁的级别 只升不降(除非发生批量撤销等特殊情况)
无锁#
对象创建时处于无锁状态,没有任何同步的开销
偏向锁#
触发条件:
-
JVM 开启
-xx:+UseBiasedLocking(JDK 15 后默认关闭) -
第一个线程进入 synchronized 块
JVM 将对象 Mark Word 中的 偏向锁标志设为 1,并且该线程下次再进来的时候就无需CAS,直接就可以获取到锁
轻量级锁#
触发条件:
- 当第二个线程尝试获取锁时,偏向锁被撤销,升级为轻量级锁
- 偏向锁在jvm上被关闭的时候
会通过 CAS自旋获取锁
CAS:核心思想是乐观锁。尝试将对象头中的 Lock Record 指针指向自己线程的锁记录,如果获取失败线程会自旋等待
重量级锁#
触发条件:
- 多个线程竞争轻量级锁
- 线程自旋超过阈值(默认10次,可以通过
-XX:PreBlockSpin调整)
Lock#
🔑
Lock是“程序员可控的锁”,而synchronized是“JVM 自动管理的锁”。
Java 中的 Lock 接口是 JDK 1.5 引入的,比 synchronized 更灵活、功能更强大的显式锁机制。它提供了对锁的精细化控制,支持尝试获取锁、可中断等待、超时获取、公平锁等高级特性。
核心方法#
public interface Lock { void lock(); // 阻塞获取锁(不可中断) // 可中断地获取锁。 //线程在等待获取锁的过程中,可以响应中断(interrupt()),从而提前退出等待,而不是像 lock() 那样“死等”下去 void lockInterruptibly() throws InterruptedException; boolean tryLock(); // 尝试获取锁,立即返回 true/false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 带超时的尝试 void unlock(); // 释放锁(必须手动调用!) Condition newCondition(); // 创建等待/通知条件对象}请注意:使用 Lock 必须在 finally 块中释放锁,否则可能死锁!
Lock lock = new ReentrantLock();lock.lock();try { // 临界区} finally { lock.unlock(); // 必须!}Lock 与 synchronized 区别#
相同点:
- 都可以保证线程的原子性、可见性、有序性
- synchronized 和 reentrantLock 都可以可重入
不同点
- synchronized 是JVM控制的,Lock是程序员手动控制
- Lock有
lockInterruptibly()方法,可以中断阻塞中的锁,但是synchronized 只能闷着头阻塞 - synchronized锁只能让一个线程拥有,但是Lock中的读写锁中的读锁可以被多个线程持有
- Lock中可以自己设置公平锁和非公平锁,synchronized不行
我们接下来重点关注一些Lock的实现

ReentrantLock源码解析#
接下来我会逐步分析从创建ReentrantLock对象到加锁过程的源码。
构造参数#
ReentrantLock中无参构造默认创建一个不公平锁,当然我们可以传入boolean值来指定创建
public ReentrantLock() { sync = new NonfairSync();}
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}这里的NonfairSync()、FairSync()方法都是ReentrantLock内部实现的,我们继续分析
NonfairSync和FairSync#
acquire(int arg) 是AbstractQueuedSynchronizer(AQS)类里面的方法,他调用了tryAcquire(int arg)。Sync实现了tryAcquire(int arg)
//非公平锁static final class NonfairSync extends Sync {
final void lock() { //判断是不是第一次获取锁,如果是之直接独占 //compareAndSetState方法的意思是:以原子的方式对比state的值是否是0.如果是的话就设置为acquires if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
//可以看到 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }}=====================================================================//公平锁static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L;
final void lock() { acquire(1); }
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //核心!!!!!!! //先判断是否有线程排队,然后再对比state的值 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //这说明线程有锁 else if (current == getExclusiveOwnerThread()) { //增加重试的次数 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); //次数设置到state里面 setState(nextc); return true; } return false; }}可以看到,公平锁的实现和非公平锁的本质区别是tryAcquire()方法的实现。
Sync#
abstract static class Sync extends AbstractQueuedSynchronizer {
//获取锁 abstract void lock();
//非公平的方式获取锁 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //获取state值,这个state是记录线程获取的次数 int c = getState(); if (c == 0) { //compareAndSetState方法的意思是:以原子的方式对比state的值是否是0.如果是的话就设置为acquires if (compareAndSetState(0, acquires)) { //设置锁被当前线程独占 setExclusiveOwnerThread(current); return true; } } //这说明线程有锁 else if (current == getExclusiveOwnerThread()) { //增加重试的次数 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); //次数设置到state里面 setState(nextc); return true; } return false; }
}总结#
读完源码相信大家对ReentrantLock有了新的理解。下面有两个问题请大家思考:
- reentrantlock怎么做到锁重入的?
- 公平锁和非公平锁的原理是什么?
读写锁 ReentrantReadWriteLock#
之前我们提到的锁都是排他锁,这次这个锁是可以允许多个线程进行读访问的。但是在写线程访问的时候,所有的读写都被阻塞
实现原理#
他的原理类似我们上面讲到的ReentranLock,也是内部维护了一个AQS,只不过这次AQS的state属性不能通过累加了,而是高16位维护读锁,低16位维护写锁来实现读写分离。
读锁支持重入,并且不管什么线程获取,读状态都增加,写锁不支持重入
读锁#
- 没有写线程访问的时候,读锁随时都可以获取到
- 如果当前线程获取读锁的时候,已经有写锁占用,那么进入等待状态
- 读锁支持重入,并且不管什么线程获取,读状态都增加
写锁#
- 写锁是支持重进入的排他锁,如果当前线程获取写锁,那么写状态增加
- 当写的状态为 0 时释放锁
锁降级#
锁降级指的是写锁降级为读锁。当前拥有写锁的线程,再获取一次读锁,然后释放自己写锁的过程
流程:拥有写锁的线程 获取读锁 释放写锁