团队简介
我们是光大科技有限公司智能化平台团队区块链项目组,致力于构建集团区块链基础设施平台,提供区块链技术在金融行业的完备解决方案,降低区块链的开发、运维难度,助力集团数字化转型。我们团队拥有经验丰富的区块链领域技术专家和研发工程师,将不定期分享区块链及相关领域的技术文章和解决方案,和大家共同探索金融行业区块链技术的落地实践和发展趋势。
线程安全是Java语言中一个比较重要的点,本篇文章我将结合《一个Java对象究竟占用多大内存》中对象在内存中的分布讲述下使用synchronized关键字时锁升级的过程。
从JDK1.5到JDK1.6 版本HotSpot虚拟机,开发团队花费了大量精力对锁进行技术优化,从而减少竞争带来的上下文切换,所以JDK1.6版本以后synchronize加锁的效率有了明显的提升。
锁升级过程中涉及到以下四种锁状态:无锁(初始状态)、偏向锁、轻量级锁、重量级锁。(级别依次升高)
-
偏向锁:大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁。(默认开启,jvm设置参数为-XX:-UseBiasedLocking)
-
轻量级锁:用来解决在没有多线程竞争的情况下,重量锁使用操作系统互斥量产生的性能消耗问题。
-
重量级锁:重量级锁也就是通常说的synchronized对象锁,每个对象都存在着一个 monitor 与之关联。
锁升级的过程即为:无锁状态→偏向锁状态→轻量锁状态→重量级锁状态。
《一个Java对象究竟占用多大内存》文章中已经介绍过Java对象从整体上可以分为三个部分,对象头、实例数据和对齐填充,而对象头又分为:Mark Word、类型指针(Klass Pointer)和数组长度(length)三部分。
下表是Mark Word具体的字段及所代表的含义。(下图为64位操作系统,32位操作系统与此有所差别)
无锁状态代码示例
/
publicclass App {
public static void main(String[] args) throws InterruptedException{
DemoA a = new DemoA();
System.out.println(a.hashCode());
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
classDemoA {
boolean a = true;
}
我们同样使用前文中提到openJDK的JOL工具包(JOL全称为Java Object Layout,是分析JVM对象内存分布的工具)。需要提及的是代码中的4, 5行代码的顺序不能互换,否则将导致内存分布结果改变。感兴趣的朋友可以自己调试并分析下产生不同结果的原因(友情提示:Java类的加载)。
图1:无锁状态下对象的内存分布
图1中显示对象的hashcode为201869954,经过16进制转换后为0x7852e922,与内存分布中的值一致,从而证明无锁状态下对象的hashCode会被存储在Mark Word中,并占用31比特位。此时,锁标志状态为图中的01。
偏向锁状态示例代码
/
public class App {
static DemoA a = new DemoA();
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (a){
System.out.println(\"t1:\");
}
}
};
t1.start();
t1.join();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
图2:偏向锁状态下的内存分布
图2中可以看出,在偏向锁状态下锁状态的标志位为01,前一比特位值为1表示当前是偏向锁,与前文介绍一致。
轻量级锁状态代码片段
/
public class App {
static DemoA a = new DemoA();
static volatile int flag = 1;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (a){
System.out.println(\"t1\");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
flag = 2;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
while (flag!=2) {
}
synchronized (a){
System.out.println(\"t2\");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t1.start();
Thread.sleep(100);
t2.start();
}
}
图3:轻量级锁状态的内存分布
从图3中可以看到当锁从偏向锁升级到轻量级锁以后,锁状态的标志位变为00。重量级锁的演示过程这里就不再赘述。
小结
通过以上代码我们验证了synchronized锁升级过程中,对象在内存分布中的变化。了解synchronized锁以及后续要讲到的lock锁对于处理并发过程中锁的使用有着重要意义。
往期推荐
29
10-2020
统一日志中心关键设计揭秘 -索引生命周期管理
统一日志中心是高效的日志收集和分析系统,可以提供实时日志查询以及海量的日志存储。想要实现这样的日志中心,索引管理是非常重要的。
21
10-2020
漫谈Redis与缓存
缓存机制为降低应用程序对物理数据源访问的频次从而提高应用的运行性能,已成为系统架构中不可或缺的一部分。而后端架构中持久层缓存目前应用最为广泛的产品就是redis,本文主要介绍其在后端缓存中起到的举足轻重的地位。
14
10-2020
企业级监控管理平台建设内幕
本文主要介绍企业级监控管理平台建设要点和经验,通过开源+自研的方式实现全面自主可控、分布式高效处理、灵活易用的统一监控管理平台,以适应当前复杂、异构、多变的IT环境。
30
09-2020
CPU虚拟化
云计算的浪潮已经来临,作为计算机核心配件的CPU,其虚拟化技术也被推在了浪尖上,且存在很大发展空间。CPU虚拟化就是将硬件中的CPU资源逻辑抽象化成虚拟资源,供虚拟机使用。本文主要从实现方式、工作原理等方面对CPU的三种虚拟化方式进行介绍。
欢迎关注EBCloud
作者 | 张宏源
原创文章,作者:EBCloud,如若转载,请注明出处:https://www.sudun.com/ask/33534.html