synchronized在jdk1.6之前,一直都是重量级锁。为什么称之为重量级锁呢,主要是因为锁的资源是通过操作系统去申请 ,所以比较重量级。
在jdk1.6之后,jvm对synchronized进行了升级,对锁的粒度进行了细化,分为无锁,偏向锁,轻量级锁,重量级锁。性能得到了很大的提升。
锁升级的过程是怎样的呢?
主要一开始是无锁的状态,如果偏向锁未启动,当有其他线程竞争时,会升级为轻量级锁,如果偏向锁启动,则会升级为偏向锁,如果竞争激烈则会从偏向锁升级为重量级锁,如果不激烈则有偏向锁升级为轻量级锁,再由轻量级锁升级为重量级锁。
过程主要如下,参考马士兵老师的锁升级过程
接下来我们就要验证下锁升级的过程。在验证锁升级的过程之前,先了解下java对象的结构,因为锁的信息就是放在对象信息的markword中。
锁状态 | 25位 | 31位 | 1位 | 4bit | 1bit 偏向锁位 | 2bit 锁标志 | |
无锁 | unused | hashcode(如果有调用) | unused | 分代年龄 | 0 | 0 | 1 |
| | | | | | | |
锁状态 | 54位 | 2位 | 1位 | 4bit | 1bit 偏向锁位 | 2bit 锁标志 | |
偏向锁 | 当前线程指针 | Epoch | unused | 分代年龄 | 1 | 0 | 1 |
| | | | | | | |
锁状态 | 62位 | 2bit 锁标志 | |||||
轻量级锁 | 指向线程栈中Lock Record的指针 | 0 | 0 | ||||
重量级锁 | 指向互斥量(重量级锁)的指针 | 1 | 0 | ||||
GC标记信息 | CMS过程用到的标记信息 | 1 | 1 |
根据markword的结构可以看到低位的2位分别表示锁的信息,第三位表示偏向锁的信息。
接下来我们来验证下锁升级的过程,这里需要借助下JOL这个工具,打印出对象的信息。在maven中添加依赖。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
现在来验证下锁升级过程。
先创建一个对象和方法,然后再多线程调用MyObject这个对象信息,再打印出对象信息,看看锁升级的过程。
public class MyObject {
public synchronized void test(){
System.out.println("test");
}
}
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.List;
public class TestSynchronizedUpgrade {
public static void main(String[] args) throws InterruptedException {
MyObject o = new MyObject();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(() -> o.test());
threads.add(thread);
}
for (Thread thread : threads) {
thread.start();
// ClassLayout输出对象信息
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
运行输出为:
test.jol.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
test.jol.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
test.jol.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
test.jol.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
test
test
test
test
test.jol.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 88 ef 3c 1c (10001000 11101111 00111100 00011100) (473755528)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
test
test.jol.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 8a 36 88 18 (10001010 00110110 10001000 00011000) (411580042)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
我们先挑出来一个看,看到第三行的数据,前四个字节的值为01 00 00 00 这是16进制数据后面括号中 (00000001 00000000 00000000 00000000)表示的二进制数据,也就是bit的数据,也就是低位三个数据是001,对照上面markword的值可以知道,开始的时候是无锁的状态。在运行到一段时间之后,低位会变成00也就是轻量级锁,再后面会变成10,也就是升级成了重量级锁。
那为什么没有偏向锁呢?查了资料发现其实是jdk在开始时偏向锁是不启动的,可能是在开始运行是并没有什么锁的竞争,所以偏向锁没有启动,当运行一段时间之后才会启动,大概是4秒之后才会启动偏向锁。
但是我在测试偏向锁的时候,发现会偏向锁变成无锁,再变成轻量级锁,再变成无锁,这点有点不太能理解,如果有人明白告知下,不胜感激!