关于Java多线程锁的升级原理,这篇文章会让你另有收获
# 2.1 Java对象内存布局 {#_2-1-java对象内存布局}
在了解锁升级原理之前我们首先要了解一下Java对象在内存中的布局
对象头用于存储对象的元数据信息,包括运行时数据和类型指针、实例数据存储的是真正有效数据、对齐填充主要补充字节,使得内存所占字节能被8整除。
其中对象头Header占12个字节:Mark Word占8个字节,类型指针class pointer占4个字节(默认经过了压缩,如果不开启压缩占8个字节)
实例对象按实际存储有不同大小,对象为空时等于0。
Padding表示对齐,当此时内存所占字节不能被8整除时补上相应字节数。
# 2.2 synchronized锁升级 {#_2-2-synchronized锁升级}
Java中锁升级的最佳实例就是synchronized,之所以要讲上面的内存布局,是因为synchronized把锁信息存放在对象头的MarkWord中。
在早期的jdk版本中,synchronized是一个重量级锁,保证线程的安全但是效率很低。后来对synchronized进行了优化,有了一个锁升级的过程:
无锁态(new)-->偏向锁-->轻量级锁(自旋锁)-->重量级锁
通过MarkWord中的8个字节也就是64位来记录锁信息。
锁升级过程详解: 当给一个对象增加synchronized锁之后,相当于上了一个偏向锁。
当有一个线程去请求时,就把这个对象MarkWord的ID改为当前线程指针ID(JavaThread),只允许这一个线程去请求对象。
当有其他线程也去请求时,就把锁升级为轻量级锁。每个线程在自己的线程栈中生成LockRecord,用CAS自旋操作将请求对象MarkWordID改为自己的LockRecord,成功的线程请求到了该对象,未成功的对象继续自旋。
如果竞争加剧,当有线程自旋超过一定次数时(在JDK1.6之后,这个自旋次数由JVM自己控制),就将轻量级锁升级为重量级锁,线程挂起,进入等待队列,等待操作系统的调度。
# 2.3 锁消除 {#_2-3-锁消除}
说了锁升级过程,有必要说一下说一下锁消除和锁粗化。
在某些情况下,如果JVM认为不需要锁,会自动消除锁,比如下面这段代码:
public void add(String a,String b){
StringBuffer sb=new StringBuffer();
sb.append(a).append(b);
}
StringBuffer是线程安全的,但是在这个add方法中stringbuffer是不能共享的资源,因此加锁只会徒增性能消耗,JVM就会消除StringBuffer内部的锁。
# 2.4 锁粗化 {#_2-4-锁粗化}
在某些情况下,JVM检测到一连串的操作都在对同一个对象不断加锁,就会将这个锁加到这一连串操作的外部,比如:
StringBuffer sb=new StringBuffer();
while(i<100){
sb.append(str);
i++;
}
上述操作StringBuffer每次添加数据都要加锁和解锁,连续100次,这时候JVM就会将锁加到更外层(while)部分。