一、概述

前面一篇文章总结了synchronized在JDK1.6中其中一个优化-锁升级。除了锁升级的优化,JDK官方还引入了自旋锁、自适应自旋锁、锁擦除、锁粗化,本篇文章将挨个介绍这些优化,最后会给出一些我们平时使用synchronized实现同步锁需要注意的地方。

二、自旋锁

【a】什么是自旋锁?

synchronized底层是通过monitor指令实现的,我们都知道monitor指令会阻塞和唤醒线程,线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,这些操作给系统的并发性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间阻塞和唤醒线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程进行自旋,这就是所谓的自旋锁。

自旋锁在JDK 1.4.2中就已经引入 ,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK 6中 就已经改为默认开启了。自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长。那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性 能上的浪费。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次,用户可以使用参数**-XX : PreBlockSpin**来更改。

【b】适应性自旋锁

在JDK 6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100次循环。另外,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。

三、锁擦除

【a】什么是锁擦除?

锁消除是指虚拟机即时编译器(JIT)在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。

【b】锁擦除案例演示

public class LockErasureDemo {public static void main(String[] args) {String contact = contact("aa", "bb", "cc");System.out.println(contact);}private static String contact(String s1, String s2, String s3) {StringBuffer stringBuffer = new StringBuffer();return stringBuffer.append(s1).append(s2).append(s3).toString();}}

观察上面的代码,我们都知道StringBuffer的append方法是加了synchronized的同步方法:

//java.lang.StringBuffer#append(java.lang.String)
public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}

这里锁住的对象其实就是stringBuffer这个局部变量,因为是局部变量,所以每个线程进来生成的stringBuffer对象不同,所以这里不存在锁竞争现象,JVM底层会将这个锁进行擦除。

四、锁粗化

【a】什么是锁粗化?

原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。大部分情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。

【b】锁粗化案例演示

public class LockCoarseningDemo {public static void main(String[] args) {StringBuffer stringBuffer = new StringBuffer();for (int i = 0; i < 100; i++) {stringBuffer.append(i);}System.out.println(stringBuffer.toString());}
}

观察上面的案例,for循环执行100次,我们都知道StringBuffer的append方法是synchronized同步方法,所以将会重复执行100次加锁操作,这显然不太好。为了避免重复的加锁解锁,JVM可能会将上面的代码优化成下面这样:

public class LockCoarseningDemo {public static void main(String[] args) {StringBuffer stringBuffer = new StringBuffer();synchronized (LockCoarseningDemo.class) {for (int i = 0; i < 100; i++) {stringBuffer.append(i);}}System.out.println(stringBuffer.toString());}
}

JVM主要做了两件事情:
1、去掉append方法的synchronized同步操作;
2、在for循环最外层加一把锁,这样的话就只需要进行一次加锁解锁操作;

五、使用synchronized注意点

【a】减少synchronized的作用范围

同步代码块中尽量短,减少同步代码块中代码的执行时间,减少锁的竞争。

【b】降低synchronized锁的粒度

将一个锁拆分为多个锁提高并发度。

Hashtable hs = new Hashtable();
hs.put("aa", "bb");
hs.put("xx", "yy");

如上代码。Hashtable我们都知道它内部的增删查改方法都加了synchronized同步锁,这样同一时刻只能有一个线程操作其中的元素,其他线程只能等待。
HashTable:锁定整个哈希表,一个操作正在进行时,另一个操作也同时锁定,效率低下。

JDK官网中还有一个类:ConcurrentHashMap,它采用了分段锁的思想,没有锁住整个哈希表,这样一个线程在其中一个桶里面添加元素,另外一个线程操作别的桶的数据不需要等待,因为两个线程锁住的不是同一个桶对象,不冲突,这样加锁效率比较高。

【c】读写分离

读写分离指的是读取操作时不加锁,写入和删除操作时加锁。例如ConcurrentHashMap,CopyOnWriteArrayList和CopyOnWriteSet等等。

【d】synchronized() 括号中的对象不能为null

private final Object obj = null;
public static void main(String[] args) {synchronized(obj){}
}

【e】注意避免多个锁交叉导致死锁

private final Object obj1 = new Object();
private final Object obj2 = new Object();public void a(){//先加obj1锁,再请求obj2锁,造成循环等待  synchronized(obj1) {synchronized(obj2) {}}
}public void b(){//先加obj2锁,再请求obj1锁,造成循环等待synchronized(obj2) {synchronized(obj1) {}}
}

【f】synchronized关键字不能被多态继承

如果父类中的某个方法是synchronized的,而子类中覆盖了这个方法,那么默认情况下子类中的这个方法并不是synchronized的,必须显式的在子类的这个方法中加上synchronized关键字才行。不过子类的那个方法也可以通过调用父类中该相应的方法来实现synchronized效果。

以上就是关于自旋锁、锁擦除、锁粗化以及平时使用synchronized需要注意的一些地方,希望对大家有所帮助。

JDK6中synchronized优化之自旋锁、锁擦除、锁粗化相关推荐

  1. 什么是自旋锁+自旋锁和互斥锁的区别

    文章目录 本文链接 什么是自旋锁 参考链接 自旋锁和互斥锁的区别 参考链接 本文链接 击打 什么是自旋锁 多线程对共享资源访问, 为防止并发引起的相关问题, 常引入锁的机制来处理并发问题.   获取到 ...

  2. Java 乐观锁和悲观锁

    文章目录 Java 乐观锁和悲观锁 1.悲观锁 2.乐观锁 2.1 CAS 2.2 模拟CAS算法 2.3 JUC 2.4 CAS中的ABA问题 2.5 使用CAS会引发的问题 Java 乐观锁和悲观 ...

  3. JVM中锁优化,偏向锁、自旋锁、锁消除、锁膨胀

    本文将简单介绍HotSpot虚拟机中用到的锁优化技术. 自旋锁 互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力.而在很多 ...

  4. Java中的锁机制 -- 乐观锁、悲观锁、自旋锁、可重入锁、读写锁、公平锁、非公平锁、共享锁、独占锁、重量级锁、轻量级锁、偏向锁、分段锁、互斥锁、同步锁、死锁、锁粗化、锁消除

    文章目录 1. Java中的锁机制 1.1 乐观锁 1.2 悲观锁 1.3 自旋锁 1.4 可重入锁(递归锁) 1.5 读写锁 1.6 公平锁 1.7 非公平锁 1.8 共享锁 1.9 独占锁 1.1 ...

  5. Java中的锁原理、锁优化、CAS、AQS详解

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:景小财 www.jianshu.com/p/e674ee68 ...

  6. 线程安全(中)--彻底搞懂synchronized(从偏向锁到重量级锁)

    接触过线程安全的同学想必都使用过synchronized这个关键字,在java同步代码快中,synchronized的使用方式无非有两个: 通过对一个对象进行加锁来实现同步,如下面代码. synchr ...

  7. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等...

    http://blog.51cto.com/13919357/2339446 Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容 ...

  8. 锁优化:逃逸分析、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁

    1. 逃逸分析 Escape Analysis 1.1 逃逸分为两种: 方法逃逸:当一个对象在方法中被定义后,可能作为调用参数被外部方法说引用. 线程逃逸:通过复制给类变量或者作为实例变量在其他线程中 ...

  9. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

    一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本质 ...

  10. Java中的锁[原理、锁优化、CAS、AQS]

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:用好Java中的枚举,真的没有那么简单!个人原创+1博客:点击前往,查看更多 作者:高广超 链接:https:/ ...

最新文章

  1. Netscaler 认证,访问报http 5000 内部错误
  2. 20分钟一键自动化部署10台集群规模实战(隆重分享)
  3. android5.1和ios差距,Android 5.1和IOS运行流畅度比较Android获胜!
  4. 如何在CentOS6.2上安装并运行飞鸽传书
  5. Config Sharepoint 2013 Workflow PowerShell Cmdlet
  6. 产品微操的艺术:提高核心指标的5个需求原理(1~5完)
  7. ORACLE会话数、连接数配置
  8. 微信小程序获取用户唯一openid,包含java
  9. java跨平台的特性_【简答题】什么是跨平台特性?Java怎样实现跨平台特性?
  10. TMS320F28335——IO控制/定时计操作
  11. 聚类热图分类注释_Python可视化matplotlibamp;seborn15-聚类热图clustermap(建议收藏)...
  12. Visual Studio Code是什么
  13. Ubuntu下hadoop2.4搭建集群(单机模式)
  14. eclipse 2020版 安装与配置完美教程
  15. poj2187 旋转卡(qia)壳(ke)
  16. 爬取DMP_ISV版(达摩盘服务商版)画像数据
  17. 深圳大澳湾度假团建攻略
  18. 知乎上关于电子商务话题的精彩问答
  19. FairyGUI摇杆
  20. 手势识别Python-OpenCV

热门文章

  1. 加载远程图片_Cocos Creator工程JavaScript实现远程图片的加载
  2. java 计划任务_Java实现定时任务的几种方案
  3. Pyspark:电影推荐
  4. 阿里云云计算 30 AS的配置
  5. 翻译:YOLOv5 新版本——改进与评估
  6. 主板检测卡c5_电脑开机停在主板logo
  7. 393.UTF-8编码验证
  8. 182.查找重复的电子邮箱
  9. ndarray负值统一置0,正数不变
  10. candence的图纸大小设置_Revit出图通用步骤5_图纸布局