19斤盐先吃9斤再吃4斤一共吃了多少斤呢(19 Java内存模型与线程_JVM层面的锁优化)
1 锁优化历史
synchronized 从 JDK1.0到JDK1.5 ,效率低 JDK1.5到JDK1.6 ,JVM团队对synchronized进行深度优化 ,加入了:适应性自旋 、锁消除 、锁膨胀 、轻量级锁 、偏向锁 等优化技术 JDK1.5 开始 ,加入java.util.concurrent ,提供API层面的轻量级锁应用为什么优化synchronized?
互斥同步对性能最大的影响是阻塞的实现 ,挂起线程和恢复线程的操作都需要转入内核态中完成 ,这些操作给Java虚拟机的并发性能带来了很大的压力 。
2 自旋锁与自适应自旋
2.1 关于自旋锁
自旋锁历史进程:首次出现在JDK1.4.2 ,但默认关闭(使用-XX:+UseSpinning参数开启) ,在JDK6开始默认开启 。
自旋锁实现逻辑:如果锁被其它线程占有 ,那么本线程不放弃处理器的执行时间 ,并执行一个忙循环(自旋) ,直至得到锁 。
自旋锁弊端:如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源 ,带来性能的浪费
自旋锁优化:
引入自旋次数:如果自旋超过了限定的次数仍然没有成功获得锁 ,就应当使用传统的方式去挂起线程 。自旋次数的默认值是10次,可以启用参数-XX:PreBlockSpin来更改 。(次数在JDK1.4.2已经实现) 引入了自适应自旋2.1 自旋锁优化:自适应自旋
原来:所有线程的自旋时间统一的(PreBlockSpin配置值 * 单次自旋时间)
自适应自旋:自旋的时间不固定 ,JVM根据前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定 。
自适应自旋情形: 例如1:线程A 、B ,锁L 。A先通过自旋获取锁 ,那么JVM认为B也能通过自旋获取到锁 ,随即给B分配自旋时间 ,并且这个时间也是综合之前的自旋时间 例如2:锁L ,线程A B C 通过自旋 ,都没有获取到锁 ,那么JVM会对后续的线程省略自旋过程 ,以免浪费处理器资源3 锁消除
定义:指虚拟机即时编译器在运行时 ,对一些代码要求同步 ,但是对被检测到不可能存在共享数据竞争的锁进行消除 。
判断依据:源于逃逸分析的数据支持 ,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到 ,那就可以把它们当作栈上数据对待 ,认为它们是线程私有的,同步加锁自然就无须再进行 。
举例:
一段看起来没有同步的代码:
public String concatString(String s1, String s2, String s3) { return s1 + s2 + s3; }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(); } //append()方法有synchronize修饰分析:
jvm观察变量sb ,经过逃逸分析后会发现它的动态作用域被限制在concatString()方法内部 。可以采用无锁操作 。
在解释执行时这里仍然会加锁 ,但在即时编译之后 ,这段代码就会忽略所有的同步措施而直接执行 。锁消除总结:
JVM会进行逃逸分析 ,符合条件的 ,在编译为机器码时 ,会消除所有同步措施
4 锁粗化
定义:假如一串零碎的操作都对同一个对象加锁 ,JVM会把加锁同步的范围扩展(粗化)到整个操作序列的外部
举例:
例如上面concatString()方法 ,将锁扩展到第一个append()操作之前最后一个append()操作之后 ,这样只需加锁一次5 轻量级锁
逻辑:无竞争的情况下使用CAS操作去消除同步使用的互斥量
轻量级锁提升性能的依据:“对于绝大部分的锁 ,在整个同步周期内都是不存在竞争的 ”这一经验法则
过程分析:后续补充6 偏向锁
逻辑:在无竞争的情况下把整个同步都消除掉 ,连CAS操作都不去做
“偏 ”的理解:锁会偏向于第一个获得它的线程,如果在接下来的执行过程中 ,该锁一直没有被其他的线程获取 ,则持有偏向锁的线程将永远不需要再进行同步。过程分析:后续补充
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!