synchronized锁优化
1.自旋锁和自适应自旋锁
sync在JDK1.6之前之所以被称为重量级锁,是因为对于互斥同步的性能来说,影响最大的就是阻塞的实现。挂起线程与恢复线程的操作都需要转入内核态中完成。从用户态转入内核态是比较耗费系统性能的。
研究表明,大多数情况下,线程持有锁的时间都不会太长,如果直接挂起OS层面的线程可能会得不偿失,毕竟OS实现线程之间的切换需要从用户态转换为核心态。这个转换时间成本相对较高。自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环,使当前线程不放弃处理器的执行时间(这也是称为自旋的原因),在经过若干循环后,如果得到锁,就顺利进入临界区。
但是阿, 自旋不能替代阻塞。首先,自旋需要多处理器或者一个处理器多个核心的CPU环境。这样才能保证两个及以上的线程并行执行(一个是获取锁的执行线程,一个是进行自旋的线程)除了对处理器数量有要求外,自旋虽然避免了线程切换的开销,但他是要占用处理器时间的,因此,如果锁被占用的时间短,自旋的效果才好,否则只是白白占用了CPU资源,带来性能的浪费。
那么自旋需要有一定的限度,如果自旋超过了一定的次数之后,还没有成功获取锁,就只能进行挂起了,这个次数默认是10.
在JDK1.4.2 引入了自旋锁,在JDK1.6引入了适应性自旋锁。自适应意味着自旋的时间不再固定。
适应性自旋锁:
如果同一个锁对象上,自旋等待刚刚成功获取锁,并且持有锁的线程正在运行,那么虚拟机就会认为此次自旋也很有可能成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。如果对于某个锁,自旋很少成功获取过,那么在以后获取这个锁时将可能自动省略掉自旋过程,以免浪费处理器资源。有了自适应自旋,随着程序运行和性能监控不断完善,虚拟机对程序锁的状况预测就会越来越精准,虚拟机也会越来越聪明。
锁消除
消除锁是更加彻底的锁优化。JVM在JIT编译时通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没必要的锁。
锁消除的主要判断依据是逃逸分析技术的支持。
也许你疑惑,变量是否逃逸,程序员本身应该可以判断,怎么会存在明明知道不存在数据竞争的情况下还是用同步?
public String concatString(String s1, String s2, String s3) {
return s1 + s2 + s3;
}
由于String是一个不可变类,因此字符串的连接操作总是通过新生成的String对象来进行。在JDK1.5之前,javac编译器会对String连接进行自动优化,将连接转换为StringBuffer对象的连续append操作,在JDK1.5之后,会转化为StringBuilder对象的append操作,也就是说,上述代码经过javac优化之后,会变成下面这样:
public String concatString(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
StringBuffer是一个线程安全的类,在它的append方法中有一个同步块,锁对象就是sb,但是JVM观察变量sb,发现他是一个局部变量,本身线程安全,并不需要额外的同步机制。因此,这里虽有锁,但可以被安全的清除,在JIT编译之后,这段代码就会忽略所有的同步而执行。这就是锁清除。
锁粗化
原则上,我们在使用同步块,总是建议将同步快的作用范围限制的尽量小——使需要同步的操作数量尽可能变小,在存在锁竞争的情况下,等待锁的线程可以尽快的拿到锁。
大部分情况下,上述原则都正确,但是存在特殊情况下,如果一系列下来,都对同一个对象反复加锁、解锁,甚至加锁与解锁操作出现在循环体,那即使没有线程竞争,频繁的进行互斥同步操作也会导致不必要的性能损耗。
如上述代码中的append方法。如果虚拟机探测到了这样的操作,就会把加锁的同步范围扩展(粗化)到整个操作序列的外部。
以上述代码为例,就是拓展到第一个append操作之前直至最后一个append操作之后,这样只需要加锁一次。
偏向锁会偏向第一个获取它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要进行同步。
HotSpot作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得(比如在单线程中使用StringBuffer类),为了让线程获得锁的代价更低而引入了偏向锁。当锁对象第一次被线程获取的时候,虚拟机把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取这个锁的线程ID记录在对象的Mark Word中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不用进行任何同步操作。
当有另一个线程去尝试获取这个锁时,偏向模式就宣告结束。
synchronized锁优化的更多相关文章
- 老掉牙的 synchronized 锁优化,一次给你讲清楚!
我们都知道 synchronized 关键字能实现线程安全,但是你知道这背后的原理是什么吗?今天我们就来讲一讲 synchronized 实现线程同步背后的原因,以及相关的锁优化策略吧. synchr ...
- synchronized 锁优化
synchronized 在jdk 1.7之前是重量级锁,独占锁,非公平锁.jdk1.7之后,synchronized引入了 偏向锁,自旋锁,轻量级锁,重量级锁 自旋锁 当线程在获取锁的时候,如果发现 ...
- synchronized锁详解
synchronized的意义 解决了Java共享内存模型带来的线程安全问题: 如:两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?(针对这个问题进行分析 ...
- Java并发编程:synchronized和锁优化
1. 使用方法 synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面: 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区 保 ...
- Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)
不止一次的提到过,synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchro ...
- Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现
一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...
- 并发-Synchronized底层优化(偏向锁、轻量级锁)
Synchronized底层优化(偏向锁.轻量级锁) 参考: http://www.cnblogs.com/paddix/p/5405678.html 一.重量级锁 上篇文章中向大家介绍了Synchr ...
- 【转】Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本 ...
随机推荐
- Redis学习四:解析配置文件 redis.conf
一.它在哪 地址: 思考:为什么要将它拷贝出来单独执行? 二.Units单位 1 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit 2 对大小写不敏感 三.INCLUDES包 ...
- Spring Aop、拦截器、过滤器的区别
Filter过滤器:拦截web访问url地址.Interceptor拦截器:拦截以 .action结尾的url,拦截Action的访问.Spring AOP拦截器:只能拦截Spring管理Bean的访 ...
- js鼠标自定移入输入框文本框光标自动定位到文本框
1.干货直接上 选中输入框设置如下: document.getElementById("Text1").focus();
- Linux下简单粗暴使用rsync实现文件同步备份【转】
这篇来说说如何安全的备份,还有一点不同的是上一篇是备份服务器拉取数据,这里要讲的是主服务器如何推送数据实现备份. 一.备份服务器配置rsync文件 vim /etc/rsyncd.conf #工作中指 ...
- python小工具之读取host文件
# -*- coding: utf-8 -*- # @Time : 2018/9/12 21:09 # @Author : cxa # @File : readhostfile.py # @Softw ...
- iOS 里const在修饰对象时候的用法
玩iOS的小伙伴对const应该很不陌生, 在声明全局常量的时候很多时候都会用到, 但是有时候修饰对象很迷惑下面是个人总结, 下面的地址都是模拟的 1. const NSString *str1 = ...
- 使用脚本实现killproc的功能
在shell提示符号下输入type killproc,会发现killproc实在 /sbin/目录下,通过man killproc可以查看这个脚本(姑且这么称为脚本)的用法,现在,把这个脚本的实现过程 ...
- RabbitMQ--Hello world!(一)
Introduction RabbitMQ is a message broker. The principal idea is pretty simple: it accepts and forwa ...
- @PrePersist
@PrePersistpublic void prePersist() { updatedAt = new Timestamp(System.currentTimeMillis()); created ...
- ubuntu 创建容器 并ssh 连接容器
1.下载镜像:docker search ubuntu docker pull ubuntu 2. 创建容器 docker run --name spider_frame -p 8888:8888 - ...