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。在这个状态下,未抢到锁的线程都会被阻塞。