synchronized与锁升级
Java对象头
Java对象在内存中有额外的对象头的占用,为了8字节对齐,还会进行数据填充:
-
对象头:
64位系统:
Mark Word
(8) + 对象指针(8)(没有开启指针压缩,开启后对象指针为4) -
实例数据
-
对齐填充
synchronized锁状态
Mark Word储存内容
锁状态 | 储存内容 | 锁标记位 |
---|---|---|
无锁 | 对象的hashCode(调用时填充)、分代年龄,偏向锁标记(0) | 01 |
偏向锁 | 偏向线程ID、偏向时间戳、分代年龄、偏向锁标记(1) | 01 |
轻量锁 | 指向栈中锁记录(Lock Record)的指针 | 00 |
重量锁 | 指向互斥量(monitor对象)的指针 | 10 |
JVM升级锁的过程
- 当没有被当成锁时,这就是一个普通的对象,
Mark Word
记录对象的HashCode
,锁标志位是01
,是否偏向锁那一位是0
。 - 当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是
01
,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。 - 当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是
01
,是否偏向锁是1
,也就是偏向状态,Mark Word
中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。 - 当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是
Mark Word
中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word
里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。 - 偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁
Mark Word
的指针,同时在对象锁Mark Word
中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word
中的锁标志位改成00
,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。 - 轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。
- 自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为
10
。在这个状态下,未抢到锁的线程都会被阻塞。