从对象头来了解synchronize关键字里的偏向锁,轻量级锁,重量级锁
前言
本文旨在通过Java对象头来分析synchronized关键字里的偏向锁,轻量级锁和重量级锁。通过几个demo来对比各个锁的性能。
特别注意:由于网络上关于【32】位虚拟机的对象头分析文章已经很多了,并且由于博主只接触过【64】位的虚拟机,以下所有内容都是针对64位虚拟机所进行的阐述与分析。
1. Java 对象头(Java Object Header)
说到对象头,博主第一次了解是在一篇博文中,里面提到了hotspot源码。于是博主从hotspot虚拟机源码中截取了markOop.hpp中关于对象头源码的注释:
// The markOop describes the header of an object.
//
// Note that the mark is not a real oop but just a word.
// It is placed in the oop hierarchy for historical reasons.
//
// Bit-format of an object header (most significant first, big endian layout below):
//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
我们可以看到在64位虚拟机中,对象头的布局与格式根据对象状态的不同有着不同的分布,在接下来的文章中,我们将对这几种情况进行分析梳理。
1.1 什么是对象头?
因为是从对象头来了解synchronized关键字,所以我们首先需要知道什么是对象头。让我们引用OpenJDK文档(OpenJDK原文档)中关于对象头的描述:
object header
Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.)
Includes fundamental information about the heap object’s layout, type,
GC state, synchronization state, and identity hash code. Consists of
two words. In arrays it is immediately followed by a length field.
Note that both Java objects and VM-internal objects have a common
object header format.
博主译:
对象头
每个由GC管理的堆对象的通用结构。(每个oop指向一个对象头)
对象头都包括了堆对象的基本信息:布局,类型,GC状态,加锁状态和表明其身份的哈希码。
每个对象头由2部分组成(作者加:即Mark Word和Klass Pointer)。在数组中,它紧跟着一个长度字段。
请注意,Java对象和虚拟机内部对象具有通用的对象头格式。
mark word
The first word of every object header. Usually a set of
bitfields including synchronization state and identity hash code. May
also be a pointer (with characteristic low bit encoding) to
synchronization related information. During GC, may contain GC state
bits.
博主译
标记字
标记字是每个对象头的第一个对象。通常来说这一组位域(bits)包括锁状态和表明其身份的哈希码。也有可能是一个指向锁相关信息的指针(具有特征性的地位编码)。在GC过程中,可能包含GC状态的位(bit)。
klass pointer
The second word of every object header. Points to
another object (a metaobject) which describes the layout and behavior
of the original object. For Java objects, the “klass” contains a C++
style “vtable”.
作者译
类型指针
类型指针是每个对象头的第二个对象。指向用来描述布局和原始对象行为的另一个对象(一个元对象)。对于Java对象们而言,“klass”包括一个C++风格的“vtable”
简而言之,每个Java对象都有一个通用的对象头结构,里面包含了对象的布局,类型,GC状态,加锁状态,hashcode。本文中,我们也就是将通过对象头里的加锁状态来分析synchronized关键字里所维护的3种锁。
说到这里,可能大家还是有点不太清楚,那我们从下面这张图来解释吧。
在64位的虚拟机中,对象头由12个字节(Byte)组成。其中8个字节是mark word,后4个字节是klass pointer(如果没有开启指针压缩的情况klass pointer为8个字节,这里我们不考虑,之后的假设都是默认开启了指针压缩的情况)。
1.2 如何查看一个Object的对象头信息?
OpenJDK提供了一个JOL包来帮助我们以一种比较清晰明了的方式查看对象头信息。我们只需要通过下载org.openjdk.jol包或者如果你是用maven构建的项目,可以通过在pom文件中引入jol-core,添加JOL依赖来达到同样的效果。
/**
* 添加JOL依赖
**/
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol‐core</artifactId>
<version>0.9</version>
</dependency>
Demo
为了测试JOL的效果,
- 我们可以创建一个不包含任何内容的类A,然后通过JOL所提供的方法来查看A的对象头信息。
//一个空白类A
public class A {
//This is a empty Class
}
- 创建一个main方法打印A的实例对象的对象头信息
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
//JOL Demo类1
public class JOLDemo1 {
public static void main(String[] args) {
A a = new A();
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
- 得到输出的对象头信息
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
org.jlfang.concurrency.lock.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
-
结果分析: 我们看到A的实例对象一共是16个字节,其中对象头是12个字节,还有4个对齐字节(因为64位虚拟机规定:对象的大小必须是8的倍数),由于这个对象里没有任何属性和方法,所以对象的实例数据为0个字节。
说到这里,可能大家会有以下两个疑问:- 什么是对象的实例数据呢?
- 对象头里为什么是12个字节,这12个字节里到底存了些什么?
我们一个一个问题来,首先什么是对象的实例数据呢?
很简单,我们往A的实例对象里添加一个boolean字段,大家都是到boolean字段占 1个字节,我们看看输出结果会有什么变化。
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
org.jlfang.concurrency.lock.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Process finished with exit code 0
从上面的输出来看,我们很容易发现,整个对象的大小没有改变,依然是16个字节,其中对象头12个字节,boolean字段 happy 占1个字节,剩下的3个Byte是对齐字节。由此我们可以发现一个对象的布局可以粗略的分为3个部分:对象头(Object Header),对象的实例数据(Instance Data)和 对齐字节(Padding)。
解决了第一个问题,我们再来来看第二个问题,对象头里为什么是12个字节,这12个字节里到底存了些什么?
其实这个问题在之前1.1节的文档中已经有了比较详细的解释,但是,只有冷冰冰的文字,怎么能说服我们呢?俗话说的好,纸上得来终觉浅,绝知此事要躬行,就让我们继续通过demo来验证这些理论吧。
根据之前的demo结果,我们已经知道一个对象头是12个字节。其中8个字节是mark word,那么剩下的4个字节就是klass pointer了。因为和锁直接相关的就是mark word,所以我们接下来着重分析一下mark word里面的信息。
在无锁情况下,markword当中的前56个bit存的是对象的hashcode,我们先来验证一下吧:
我们将A对象的实例在进行hashcode运算前和运算后的对象头打印出来,然后手动把对象头的前56个bit转换为hashcode,看看和JVM计算的hashcode是否相同。
- 先新建一个类,写入我们的main方法
import org.jlfang.concurrency.utils.HashUtil;
import org.openjdk.jol.info.ClassLayout;
public class JOLDemo2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
A a = new A();
System.out.println("before hash");
//未计算hashcode之前的对象头
System.out.println(ClassLayout.parseInstance(a).toPrintable());
//JVM计算的hashcode
System.out.println("JVM calculated hashcode---------0x"+Integer.toHexString(a.hashCode()));
HashUtil.countHash(a);
System.out.println("after hash");
//计算hashcode之后的对象头
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
- 然后我们创建一个HashUtil类,用于将对象头的56个bit的值转为16进制的hashcode(注意,由于hashcode在对象头中存储是小端模式,即
“数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中” (来源:百度百科)
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class HashUtil {
public static void countHash(Object object) throws NoSuchFieldException, IllegalAccessException {
// 手动计算HashCode
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long hashCode = 0;
//!!!注意,由于是小端存储,我们将从后往前计算
for (long index = 7; index > 0; index--){
//取Mark Word中的每一个Byte进行计算
hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index -1) *8);
}
String code = Long.toHexString(hashCode);
System.out.println("HashUtil calculated hashcode ---0x" + code);
}
}
- 通过运行,我们得到以下结果
before hash
org.jlfang.concurrency.lock.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
JVM calculated hashcode---------0x3581c5f3
HashUtil calculated hashcode ---0x3581c5f3
after hash
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 f3 c5 81 (00000001 11110011 11000101 10000001) (-2117733631)
4 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Process finished with exit code 0
我们发现JVM计算出的hashcode和我们HashUtil计算出的hashcode是相同的,所以我们可以确定Java对象头当中的mark work里面的后七个字节存储的是hashcode信息,那么第一个字节当中的8个bit分别村的是以下信息:分带年龄,偏向锁状态,对象状态。这些信息随着对象状态的改变也会发生相应的变化,下图是无锁状态
关于对象的状态,一共有以下五种:无锁,偏向锁,轻量锁,重量锁,GC标记。那么问题来了,从上图我们可以看到对象状态只有2个bit来表示,因此最多只能表示4个状态:00,01,10,11,那么我们要如何用2个bit表示的4个状态呢?很简单JVM通过把偏向锁和无锁表示为同一状态即都是01,然后通过前一个bit的偏向锁标示来分辨到底是无锁还是偏向锁。
2. 偏向锁
博主在之前的博文(synchronized和volatile关键字的使用)结尾提到了偏向锁,轻量级锁和重量级锁的触发条件: 当不存在线程竞争时(即只有1个线程获取被syncrhonized关键字所修饰的对象时),synchronized关键字会使用偏向锁;而当有2个或以上线程获取该对象且未发生竞争时(即2个或以上线程交替获取该对象),锁会膨胀为轻量级锁;只有当2个或以上线程获取该对象且发生竞争时,锁会最终膨胀为重量级锁。
所以我们只需要只让一个线程取获取锁,这样对象将被偏向锁锁定,说了这么多我们终于可以通过demo来查看偏向锁的对象头了,闲话少说,上代码:
import org.openjdk.jol.info.ClassLayout;
public class JOLDemo3 {
static A a;
public static void main(String[] args) {
a = new A();
System.out.println("before lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
sync();
System.out.println("after lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
public static void sync(){
synchronized (a){
System.out.println("locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
看结果:
before lock
org.jlfang.concurrency.lock.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
locking
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 80 f5 6c 03 (10000000 11110101 01101100 00000011) (57472384)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
after lock
org.jlfang.concurrency.lock.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Process finished with exit code 0
咦,打印出来的结果表明这个对象再上锁过程中(locking)变成了轻量级锁(00)的状态,这显然不符合我们的预期。博主之前提到过在有且仅有1个线程获取synchronized关键字中的对象时,该对象的锁状态为偏向锁。那么为什么会这样呢?因为JVM的偏向锁有延迟启动机制,每个对象并不会在一开始就获得偏向锁,需要在程序运行后大概4-5秒之后才会获得。同样,让我们通过demo来验证一下
相同的代码,我们只是加了5秒的睡眠时间(避免偏向锁启动延迟的方法,还有更加直接的办法,我们可以在启动时添加下列参数取消偏向锁启动延迟:”-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0“):
public class JOLDemo3 {
static A a;
public static void main(String[] args) throws InterruptedException {
//睡眠5秒,保证偏向锁启动
TimeUnit.SECONDS.sleep(5);
a = new A();
System.out.println("before lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
sync();
System.out.println("after lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
public static void sync(){
synchronized (a){
System.out.println("locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
下面是输出结果:
before lock
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
locking
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 97 02 (00000101 00111000 10010111 00000010) (43464709)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
after lock
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 97 02 (00000101 00111000 10010111 00000010) (43464709)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Process finished with exit code 0
终于,我们看到了偏向锁状态101,需要注意的时,和轻量级锁和重量级锁每次退出同步将变成无锁状态不同,当偏向锁退出同步状态后,该对象依然保持了偏向状态。
3. 轻量级锁
当偏向锁对象被第二个线程获取时,会膨胀为轻量级锁。
之前的案例其实已经验证了轻量级锁的对象头,此处就不再做演示了。
4. 重量级锁
像博主之前说的,如果2个线程发生了竞争,锁会膨胀为重量级锁。
让我们来演示以下
import org.openjdk.jol.info.ClassLayout;
public class JOLDemo4 {
static A a;
public static void main(String[] args) {
a = new A();
System.out.println("before lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
new Thread(()->{
sync();
}).start();
new Thread(()->{
sync();
}).start();
System.out.println("after lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
public static void sync(){
synchronized (a){
System.out.println(Thread.currentThread().getName()+" is locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
以下为结果
before lock
org.jlfang.concurrency.lock.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Thread-0 is locking
after lock
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 0a 36 8b 1c (00001010 00110110 10001011 00011100) (478885386)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 0a 36 8b 1c (00001010 00110110 10001011 00011100) (478885386)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Thread-1 is locking
org.jlfang.concurrency.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 0a 36 8b 1c (00001010 00110110 10001011 00011100) (478885386)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 1 boolean A.happy true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Process finished with exit code 0
很明显,当2个线程发生竞争时,锁已经膨胀为重量级锁了(10)。
5. 偏向锁,轻量级锁和重量级锁的性能对比
说了这么多,我们为什么要这么关注synchronized关键字维护的到底时哪种锁呢?各个锁之间的差异很大吗?
我们还是用demo来测试一下:我们调用同步方法10亿次,来计算10次++操作所耗的时间:
- 首先是偏向锁(特别注意启动时要加上参数:‐XX:BiasedLockingStartupDelay=20000 ‐XX:BiasedLockingStartupDelay=0 , 否则启动的将是轻量级锁)
//‐XX:BiasedLockingStartupDelay=20000 ‐XX:BiasedLockingStartupDelay=0
public class JOLDemo5 {
public static void main(String[] args) throws Exception {
A a = new A();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
a.parse();
}
long end = System.currentTimeMillis();
System.out.println(String.format("%sms", end - start));
}
}
parse方法
public class A {
int i;
public synchronized void parse(){
i++;
}
}
偏向锁结果:
1576ms
Process finished with exit code 0
- 再看轻量级锁的结果,我们只需要把偏向锁延迟启动加上就能模拟出来轻量锁的效果。
public class JOLDemo5 {
public static void main(String[] args) throws Exception {
A a = new A();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
a.parse();
}
long end = System.currentTimeMillis();
System.out.println(String.format("%sms", end - start));
}
}
轻量级锁结果
19719ms
Process finished with exit code 0
- 再来看看重量级锁,因为重量级锁需要发生线程竞争,我们通过countdownlatch来计数。
public class JOLDemo6 {
static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
public static void main(String[] args) throws InterruptedException {
final A a = new A();
long start = System.currentTimeMillis();
for(int i=0;i<2;i++){
new Thread(()->{
while(countDownLatch.getCount()>0){
a.parse();
}
}).start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(String.format("%sms", end - start));
}
}
重量级锁结果:
44406ms
Process finished with exit code 0
锁 | 偏向锁 | 轻量级锁 | 重量级锁 |
---|---|---|---|
执行10亿次++操作耗时(单位:毫秒) | 1576 | 19719 | 44406 |
小结
简而言之,偏向锁,轻量级锁和重量级锁在性能上有着巨大的差异,如何合理的利用synchronized关键字考验着每一位Java开发工程师,希望这篇博文能帮助到各位。