这一节非常重要,是整个并发锁的关键,因为重视提到无所、偏向锁、轻量锁、重量锁这些概念,很多人不知道这些东西是什么,为什么这样称呼,这一节好好看。
目录
一、概念
对象头到底是什么东西呢?我们从hotspot的源码中贴上一段描述。
// 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)
网上的那个锁的各种结构说明都是32位操作系统的,现在32位操作系统应该都已经见不到了吧。所以我们直接看64位操作系统,下面是64位操作系统的对象头的描述翻译如下。
|--------------------------------------------------------------------------------------------------------------|
| Object Header (128 bits) |
|--------------------------------------------------------------------------------------------------------------|
| Mark Word (64 bits) | Klass Word (64 bits) |默认开启指针压缩(32bits)
|--------------------------------------------------------------------------------------------------------------|
|unused:25|identity_hashcode:31(56) | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 无锁
|----------------------------------------------------------------------|--------|------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | 轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | 重量锁
|----------------------------------------------------------------------|--------|------------------------------|
| | lock:2 | OOP to metadata object | GC
|--------------------------------------------------------------------------------------------------------------|
从上图可以看到的是对象头分为 MarkWord 和 Klass Word 两部分。 KlassWord默认开启了指针压缩,实际占用空间不到64bits。没有开启指针压缩的时候是64bits。
Java的对象头在对象的不同状态下会有不同的表现形式,主要有三种状态:无锁状态、加锁状态、gc标记状态。那么我们可以理解java当中的取锁其实可以理解是给对象上锁,也就是改变对象头状态。如果上锁成功就可以进入同步代码块。
三种锁状态:偏向锁、轻量级锁、重量级锁的效率差距会让你惊讶,我们下面慢慢说。
其中
MarkWord 一般就是锁信息存储的地方,由上图看可以分成:无锁、偏向锁、轻量锁、重量锁、gc。
KlassWord 存的是指向元数据的指针,元数据就是类被JVM加载进内存中方法区的字节码,也可以理解为指向了class文件在jvm中存储的地址。
二、用代码探究原理
这块可是非常非常重要的,也是网上根本看不到的东西。我可是下苦功才弄明白的。我们先用JOL来看看对象头到底是什么样子,还好java提供了来分析java对象布局的工具JOL。利用maven引入:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
</dependency>
2.1、初探java对象头布局
我们创建一个空的Class ,代码如下:
package com.hubin.lock;
public class A {
}
在建立一个文件打印出来对象头:
package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
public class JolExample {
public static void main(String[] args) {
A a = new A();
out.println(VM.current().details());
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果:
# Using compressed oop with 0-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]
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
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
下面我们来分析下这输出都是什么意思
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] 对应的是:[Oop(Ordinary Object Pointer 普通对象指针4bytes),boolean,byte,char,short,int ,float,long,double] 。
对象头(object header)一共16Bytes。 其中有4B是对齐的字节(因为在64位虚拟机上对象的大小必
须是8的倍数),由于这个对象里面没有任何字段,故而对象的实例数据为0B。
如果我的A类里面在写几个属性值,再次运行结果如下:最后的对其字节就是用来对其boolean的。
public class A {
boolean a;
int b;
float c;
}
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int A.b 0
16 4 float A.c 0.0
20 1 boolean A.a false
21 3 (loss due to the next object alignment)
Instance size: 24 bytes
因为加的属性比较多,所以对象整体变成了24B了,但是对象头没有变还是12B,由此可以看出来对象布局大体由三部分构成,对象头、实例数据、字节对齐。
下面就仔细探讨下对象头12B的组成。
2.2对象头详解
对象头的官网说明可以看下面链接,http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
大概意思就是java对象头包含对象布局、类型、GC状态、同步状态和标识hash码。由2个word组成。
mark word 为第一个word根据文档可以得知它里面包含了锁的信息,hashcode,gc信息等等。
Klass为对象头的第二个word,主要指向对象的元数据。
假设我们理解一个对象头主要上图两部分组成(数组对象除外,数组对象的对象头还包含一个数组长度),一个java对象头多大呢?JVM源码写的很清楚,64bit。但是没有写Klass长度多少。从我们上面利用JOL打印的结果就可以看出来一个对象头是12Bytes。其中8Bytes是MarkWord,那么剩余的就是4Bytes就是Klass Word了,但是是不是最开始的图里面写的是64Bits也就是8Bytes,大家会问为啥不一样呢?因为我开始也说了一句,就是指针压缩,默认开启压缩,如果不压缩就是8Bytes了。和所相关的都在MarkWord里面所以我们着重分析下MarkWord。
1、无锁状态
无锁状态的MarkWord的每个bit的说明,前25个bit没用,也就是可以说前56个bits存的是hashcode,age占4个bits(gc标记用的,这个也就是说gc最大标记也就是15次),biased_lock表示是否是偏向锁,lock用2位来表示锁状态。
unused:25|identity_hashcode:31(56) | unused:1 | age:4 | biased_lock:1 | lock:2
但是我们看到之前的输出hashcode都是0,这是为啥呢。我们来一探究竟。
我们运行一个计算hashcode的代码示例:
package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
public class JolExample2 {
public static void main(String[] args) {
A a = new A();
out.println("before hash:");
out.println(ClassLayout.parseInstance(a).toPrintable());
out.println("jvm----hash code: "+Integer.toHexString(a.hashCode()));
//HashUtil.countHash(a); 打印对象头里面的转换成十六进制会发现跟上一句输出一样。
out.println("after hashcode:");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
我们中间输出下hashcode,这样就相当于做了一次hashcode计算。然后再看对象头信息就变了。原来都是0变成了有1出险了,利用二进制转化成十六进制来看,跟计算的a的hashcode是一样的。但是为什么是反的呢,因为window的操作系统是小端存储,小端存储就是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。
所以最开始的8位就是unused:1 | age:4 | biased_lock:1 | lock:2
然后依次是hashcode和unused。因为低位存低地址。所以大家用进制转换工具(https://tool.oschina.net/hexconvert/)就可以看到转换出来的十六进制跟打印出来的是一致的。
before hash:
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
jvm----hash code: 6ae40994
after hashcode:
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 94 09 e4 (00000001 10010100 00001001 11100100) (-469134335)
4 4 (object header) 6a 00 00 00 (01101010 00000000 00000000 00000000) (106)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 by看tes internal + 4 bytes external = 4 bytes total
当然可以用一个进制转换工具类输出也是可以的。我给出代码:
package com.hubin.lock;
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("util-----------0x"+code);
}
}
看输出可以看出来,1-9行 before hash的输出的对象头,1-7Bytes都是0,证明hashcode是没有的。打印完后再看12到19行,after hash的时候就出现值了。可以证明hashcode如果不主动计算,是不会自动生成的。
对象一共五种状态:无锁,偏向锁、轻量锁、重量锁、gc标记,但是锁状态只有2位最多只能表示4中状态,所以JVM将无锁和偏向锁用相同的lock状态表示,但是用biased_lock来区分。
2、偏向锁
看下面代码:
package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JolExample3 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
A a = new A();
out.println("before lock:");
out.println(ClassLayout.parseInstance(a).toPrintable());
synchronized (a){
out.println("locking......");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
out.println("after lock:");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
before lock:
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
locking......
after lock:
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
这个程序是调用了synchronized 方法,按理说应该a的对象头里面的锁状态应该变成了偏向锁应该是00000101才对。但是最后结果还是00000001无锁。是不是错了?其实没错,这是因为虚拟机在启动的时候对于偏向锁有延迟。比如我们在代码里面加个睡眠5s(延迟加载默认是4s),结果就是偏向锁了。当然我们还可以关掉延迟通过以下参数可以禁止延迟:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 我们加上这个参数试下结果跟我们说的一致。
before lock:
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
locking......
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 c7 02 (00000101 11100000 11000111 00000010) (46653445)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after lock:
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 c7 02 (00000101 11100000 11000111 00000010) (46653445)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
为什么偏向锁要设置延迟呢?因为JVM认为很多资源都会存在竞争关系,也就是很多代码块都会有synchronized,一个线程访问可以使偏向锁,但是多个线程访问就会升级成轻量级锁了,锁升级先要消除偏向锁,这个是非常麻烦的过程,所以jvm一般先延迟4s在启动偏向锁。偏向锁的MarkWord如下: thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2
偏向锁没有hashcode,这个说明hashcode和偏向锁无法共存。
大家肯定有个疑问? 为什么before lock 也是偏向锁,after lock也是呢?
after lock退出同步后,依然保持了偏向信息。因为偏向锁就是偏向同一个线程。 这个我们在下一章节锁膨胀在细说。
但是有个问题要注意,如果是如下的代码:
A a = new A();
Thread.sleep(5000);
这样模拟不出来延迟偏向的结果,对象a还是不可偏向的,因为a对象在睡眠之前已经被初始化了。如果将Sleep放到初始化前面的话就是可偏向的了。
3、轻量级锁
轻量锁一定是无锁状态通过CAS才能膨胀成轻量锁。如果开始是偏向锁,也会先撤销成无锁然后再CAS变成轻量锁。
package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JolExample5 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
A a = new A();
out.println("before lock:");
out.println(ClassLayout.parseInstance(a).toPrintable());
synchronized (a){
out.println("locking......");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
out.println("after lock:");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
before lock:
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
locking......
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 48 f4 39 03 (01001000 11110100 00111001 00000011) (54129736)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after lock:
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
输出结果可以看到在locking中,打印的对象头中,a对象的锁状态是轻量级锁00。轻量级锁尝试在应用层面解决线程同步问题,而不触发操作系统的互斥操作,轻量级锁减少多线程进入互斥的几率,不能代替互斥。
同步分为三种情况:
1、单线程(单线程也可以加同步块)
2、多线程交替执行,无竞争(或者自旋能够获取到锁) 一般自旋时间很短就是一个线程上下文的切换时间。 这个时候就会变成轻量级锁
3、多线程互斥执行
轻量级锁在退出同步块,后会恢复成为无锁状态。
4、重量级锁
老样子看下重量级锁的表头,这里就干脆多打印点状态。重量级锁需要去竞争资源,第一次同步等待5s,这期间再去获取对象a锁,则a就会升级成重量级锁。
重量级锁在退出同步后也是保持重量级锁状态。
package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample7 {
static A a;
public static void main(String[] args) throws Exception {
a = new A();
out.println("befre lock");
out.println(ClassLayout.parseInstance(a).toPrintable());//无锁
Thread t1= new Thread(){
public void run() {
synchronized (a){
try {
Thread.sleep(5000);
out.println("t1 release");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread.sleep(1000);
out.println("t1 lock ing");
out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁
sync(); //竞争变重量级
out.println("after lock");
//重量级锁,因为锁状态没改变回来
out.println(ClassLayout.parseInstance(a).toPrintable());
System.gc();
out.println("after gc()");
out.println(ClassLayout.parseInstance(a).toPrintable());//无锁---gc
}
public static void sync() throws InterruptedException {
synchronized (a){
out.println("t1 main lock");
out.println(ClassLayout.parseInstance(a).toPrintable());//重量锁
}
}
}
看看各个情况下对象头: 10是重量级锁状态
befre lock
com.hubin.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) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t1 lock ing
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 68 f6 ec 1b (01101000 11110110 11101100 00011011) (468514408)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t1 release
t1 main lock
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) aa 1c 36 18 (10101010 00011100 00110110 00011000) (406199466)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after lock
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) aa 1c 36 18 (10101010 00011100 00110110 00011000) (406199466)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after gc()
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 09 00 00 00 (00001001 00000000 00000000 00000000) (9)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
5、性能对比 偏向锁 VS 轻量级锁 VS 重量级锁
我们在B中加入一个计数器,并且用下面配置控制是否开启偏向
‐XX:BiasedLockingStartupDelay=20000
‐XX:BiasedLockingStartupDelay=0
package com.hubin.lock;
public class B {
int i;
public synchronized void add(){
i++;
}
}
package com.hubin.lock;
public class JOLExample4 {
public static void main(String[] args) throws Exception {
B b = new B();
long start = System.currentTimeMillis();
//调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
for (int i = 0; i < 1000000000L; i++) {
b.add();
}
long end = System.currentTimeMillis();
System.out.println(String.format("%sms", end - start));
}
}
重量级锁需要模拟2个线程来竞争资源,我们利用CountDownLatch来模拟
package com.hubin.lock;
public class C {
int i;
public synchronized void add(){
i++;
JOLExample6.countDownLatch.countDown();
}
}
package com.hubin.lock;
import java.util.concurrent.CountDownLatch;
public class JOLExample6 {
static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
public static void main(String[] args) throws Exception {
final C c = new C();
long start = System.currentTimeMillis();
//调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
for(int i=0;i<2;i++){
new Thread(){
@Override
public void run() {
while (countDownLatch.getCount() > 0) {
c.add();
}
}
}.start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(String.format("%sms", end - start));
}
}
偏向锁运行结果是: 1607ms
轻量级锁运行结果是:18598ms
重量级锁运行结果是:37371ms
差距太明显了。
6、 调用wait方法则立马变成重量级锁
看如下代码
package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample8 {
static A a;
public static void main(String[] args) throws Exception {
a = new A();
out.println("befre lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
Thread t1= new Thread(){
public void run() {
synchronized (a){
try {
out.println("before wait");
out.println(ClassLayout.parseInstance(a).toPrintable());
a.wait(); //阻塞,让出锁资源
out.println("after wait");
out.println(ClassLayout.parseInstance(a).toPrintable());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread.sleep(5000);
synchronized (a){
//唤醒
a.notifyAll();
}
}
}
结果:
befre lock
com.hubin.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) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
before wait
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) c8 f5 6b 1b (11001000 11110101 01101011 00011011) (460060104)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after wait
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) aa 32 bf 17 (10101010 00110010 10111111 00010111) (398406314)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
调用wait会立马释放锁,而调用notify的话不会立马释放锁,还是会执行完后续代码才释放锁。有个朋友正好遇到类似得一个面试题,下面来看个面试题:大家看看下面代码会有什么问题。
解析:notify方法只能唤醒一个正在等待 这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒一个,但是调用notify这个方法不会马上释放锁,必须等到Synchronized方法或者语法块执行完才真正释放。所以上面代码会造成死锁。
7、计算了hashcode就无法成为偏向锁了
看个例子
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample9 {
static A a;
public static void main(String[] args) throws Exception {
//-XX:BiasedLockingStartupDelay=0
a = new A();
out.println("befre hash");
out.println(ClassLayout.parseInstance(a).toPrintable());
a.hashCode();
synchronized (a) {
out.println("after hash");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
取消偏向锁延迟的输出结果: hash值计算完后,就是轻量级锁了。如果不计算hashcode,就是偏向锁。
befre hash
com.hubin.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 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after hash
com.hubin.lock.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) b0 f4 11 03 (10110000 11110100 00010001 00000011) (51508400)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes intern al + 4 bytes external = 4 bytes total
下面就是对象头得知识,看完这个大家应该对无所,偏向锁,轻量锁,重量锁,了如执掌了吧。下一级分析锁膨胀。