JDK6中synchronized优化之自旋锁、锁擦除、锁粗化
一、概述
前面一篇文章总结了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优化之自旋锁、锁擦除、锁粗化相关推荐
- 什么是自旋锁+自旋锁和互斥锁的区别
文章目录 本文链接 什么是自旋锁 参考链接 自旋锁和互斥锁的区别 参考链接 本文链接 击打 什么是自旋锁 多线程对共享资源访问, 为防止并发引起的相关问题, 常引入锁的机制来处理并发问题. 获取到 ...
- Java 乐观锁和悲观锁
文章目录 Java 乐观锁和悲观锁 1.悲观锁 2.乐观锁 2.1 CAS 2.2 模拟CAS算法 2.3 JUC 2.4 CAS中的ABA问题 2.5 使用CAS会引发的问题 Java 乐观锁和悲观 ...
- JVM中锁优化,偏向锁、自旋锁、锁消除、锁膨胀
本文将简单介绍HotSpot虚拟机中用到的锁优化技术. 自旋锁 互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力.而在很多 ...
- Java中的锁机制 -- 乐观锁、悲观锁、自旋锁、可重入锁、读写锁、公平锁、非公平锁、共享锁、独占锁、重量级锁、轻量级锁、偏向锁、分段锁、互斥锁、同步锁、死锁、锁粗化、锁消除
文章目录 1. Java中的锁机制 1.1 乐观锁 1.2 悲观锁 1.3 自旋锁 1.4 可重入锁(递归锁) 1.5 读写锁 1.6 公平锁 1.7 非公平锁 1.8 共享锁 1.9 独占锁 1.1 ...
- Java中的锁原理、锁优化、CAS、AQS详解
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:景小财 www.jianshu.com/p/e674ee68 ...
- 线程安全(中)--彻底搞懂synchronized(从偏向锁到重量级锁)
接触过线程安全的同学想必都使用过synchronized这个关键字,在java同步代码快中,synchronized的使用方式无非有两个: 通过对一个对象进行加锁来实现同步,如下面代码. synchr ...
- Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等...
http://blog.51cto.com/13919357/2339446 Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容 ...
- 锁优化:逃逸分析、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁
1. 逃逸分析 Escape Analysis 1.1 逃逸分为两种: 方法逃逸:当一个对象在方法中被定义后,可能作为调用参数被外部方法说引用. 线程逃逸:通过复制给类变量或者作为实例变量在其他线程中 ...
- Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本质 ...
- Java中的锁[原理、锁优化、CAS、AQS]
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:用好Java中的枚举,真的没有那么简单!个人原创+1博客:点击前往,查看更多 作者:高广超 链接:https:/ ...
最新文章
- Netscaler 认证,访问报http 5000 内部错误
- 20分钟一键自动化部署10台集群规模实战(隆重分享)
- android5.1和ios差距,Android 5.1和IOS运行流畅度比较Android获胜!
- 如何在CentOS6.2上安装并运行飞鸽传书
- Config Sharepoint 2013 Workflow PowerShell Cmdlet
- 产品微操的艺术:提高核心指标的5个需求原理(1~5完)
- ORACLE会话数、连接数配置
- 微信小程序获取用户唯一openid,包含java
- java跨平台的特性_【简答题】什么是跨平台特性?Java怎样实现跨平台特性?
- TMS320F28335——IO控制/定时计操作
- 聚类热图分类注释_Python可视化matplotlibamp;seborn15-聚类热图clustermap(建议收藏)...
- Visual Studio Code是什么
- Ubuntu下hadoop2.4搭建集群(单机模式)
- eclipse 2020版 安装与配置完美教程
- poj2187 旋转卡(qia)壳(ke)
- 爬取DMP_ISV版(达摩盘服务商版)画像数据
- 深圳大澳湾度假团建攻略
- 知乎上关于电子商务话题的精彩问答
- FairyGUI摇杆
- 手势识别Python-OpenCV