(十六)关于Java多线程锁的升级原理,这篇文章会让你另有收获

2,285 阅读3分钟

微信搜索《Java鱼仔》,每天一个知识点不错过

(一)每天一个知识点

关于Java多线程锁的升级原理,这篇文章会让你另有收获

(二)回答

2.1 Java对象内存布局

在了解锁升级原理之前我们首先要了解一下Java对象在内存中的布局 在这里插入图片描述 对象头用于存储对象的元数据信息,包括运行时数据和类型指针、实例数据存储的是真正有效数据、对齐填充主要补充字节,使得内存所占字节能被8整除。

其中对象头Header占12个字节:Mark Word占8个字节,类型指针class pointer占4个字节(默认经过了压缩,如果不开启压缩占8个字节)

实例对象按实际存储有不同大小,对象为空时等于0。

Padding表示对齐,当此时内存所占字节不能被8整除时补上相应字节数。

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 锁消除

说了锁升级过程,有必要说一下说一下锁消除和锁粗化。

在某些情况下,如果JVM认为不需要锁,会自动消除锁,比如下面这段代码:

public void add(String a,String b){
    StringBuffer sb=new StringBuffer();
    sb.append(a).append(b);
}

StringBuffer是线程安全的,但是在这个add方法中stringbuffer是不能共享的资源,因此加锁只会徒增性能消耗,JVM就会消除StringBuffer内部的锁。

2.4 锁粗化

在某些情况下,JVM检测到一连串的操作都在对同一个对象不断加锁,就会将这个锁加到这一连串操作的外部,比如:

StringBuffer sb=new StringBuffer();
while(i<100){
    sb.append(str);
    i++;
}

上述操作StringBuffer每次添加数据都要加锁和解锁,连续100次,这时候JVM就会将锁加到更外层(while)部分。