锁升级
锁升级,是JDK1.8
版本中对于synchronized
的优化。调查发现一般情况下锁的使用都是为了处理一些极端情况,但多时间,并不会出现并发争强的情况,直接是有synchronized
比较重,会影响系统性能。
升级步骤: 无锁 -> 偏向锁/匿名偏向锁 -> 轻量级锁 -> 重量级锁
升级特点:一旦升级,无法降级
锁状态对照表:(根据下图,可查对锁状态)
无锁
所有对象最初都是无锁状态。
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
偏向锁与轻量级锁
轻量级锁
当第一线程获取到锁后,锁状态由无锁转为偏向锁
。但实际发现锁状态是轻量级锁似乎没有偏向锁
。~?!
这其中涉及到jvm的偏向锁开启机制,5秒开始。5秒内,锁状态会由无锁转为轻量级锁。
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
// 获取锁
// 此时锁,却是轻量级锁
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
此时是轻量级锁
偏向锁
添加5秒睡眠后,会出现偏向锁。此时会发现,偏向锁分为两钟,一个普通偏向锁,一个是匿名偏向锁。
需要注意的是,当jvm开启偏向锁后,所有对象锁状态会自动转为匿名偏向锁
,只会变更锁状态,不会记录线程信息。
public static void main(String[] args) throws InterruptedException {
// 5秒睡眠
Thread.sleep(6000);
Object o = new Object();
// 此时锁为 匿名偏向级锁。即便是没有使用synchronized
System.out.println(ClassLayout.parseInstance(o).toPrintable());
// 此时锁为 偏向级锁
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
重量级锁
当轻量级锁进行CAS,当自旋达到一定次数后,锁升级为重量级锁。
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
Object o = new Object();
// 此时锁是 匿名偏向锁
System.out.println(ClassLayout.parseInstance(o).toPrintable());
new Thread(() -> {
// 线程拿到锁后,锁状态变为偏向级锁
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}).start();
// 尝试获取锁后,发现是锁已经被获取,已是偏向级锁
// 之后升级为 自旋锁,然后cas 一定次数后,升级为重量级锁
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
// 如果cpu好的话,此时输出可能是轻量级锁
}
}
5秒开始偏向锁设定原因
偏向锁转化为轻量级锁时,会涉及到偏向锁撤销操作。查看ClassLoader源码发现,但在大量并发加载class的情况下,锁撤销会影响性能。为了避免添加了5秒开启,让锁状态直接从无锁转化为轻量级锁。