Java中Synchronized的优化原理
我们知道,从 JDK1.6 开始,Java 对 Synchronized 同步锁做了充分的优化,甚至在某些场景下,它的性能已经超越了 Lock 同步锁。那么就让我们来看看,它究竟是如何优化的。
原本的问题
Synchronized是基于底层操作系统的 Mutex Lock 实现的,每次获取锁和释放锁的操作都会带来用户态和内核态的切换,从而增加系统性能开销。
因此,在锁竞争激烈的情况下,Synchronized同步锁在性能上就表现得非常糟糕,它也常被大家称为重量级锁。
到了 JDK1.5 版本,并发包中新增了 Lock 接口来实现锁功能,它提供了与 Synchronized 关键字类似的同步功能,只是在使用时需要显示获取锁和释放锁。
在单个线程重复申请锁的情况下,JDK1.5 版本的 Lock 性能要比 Synchronized 锁的性能好很多,也就是当时的 Synchronized 并不具备可重入锁的功能。
那么当时的 Synchronized 是怎么实现的?又为什么不具备可重入的功能呢?
Synchronized原理
JVM 中的同步是基于进入和退出管程(Monitor)对象实现的。每个对象实例都会有一个 Monitor,Monitor 可以和对象一起创建、销毁。
当多个线程同时访问一段同步代码时,多个线程会先被存放在EntryList集合(也可称为阻塞队列)中,处于BLOCKED状态的线程,都会被加入到该列表。
接下来当线程获取到对象的 Monitor 时,Monitor 是依靠底层操作系统的 Mutex Lock 来实现互斥的,线程申请 Mutex 成功,则持有该 Mutex,其它线程将无法获取到该 Mutex。
如果线程调用 wait() 方法,就会释放当前持有的 Mutex,并且该线程会进入WaitSet集合(也可称为等待队列)中,等待下一次被唤醒。此时线程会处于WAITING或者TIMEDWAITING状态,
如果当前线程顺利执行完方法,也将释放 Mutex。
总的来说,就是同步锁在这种实现方式中,因 Monitor 是依赖于底层的操作系统实现,存在用户态与内核态之间的切换(可以理解为上下文切换),所以增加了性能开销。
锁升级
为了提升性能,JDK1.6 引入了偏向锁、轻量级锁、重量级锁概念,来减少锁竞争带来的上下文切换,而正是新增的Java对象头实现了锁升级功能。
所谓锁升级,就是指
Synchronized 同步锁初始为
偏向锁,随着线程竞争越来越激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。
偏向锁
偏向锁主要用来优化同一线程多次申请同一个锁的竞争,也就是现在的Synchronized锁实际已经拥有了可重入锁的功能。
为什么要有偏向锁?因为在我们的应用中,可能大部分时间是同一个线程竞争锁资源(比如单线程操作一个线程安全的容器),如果这个线程每次都要获取锁和释放锁,那么就在不断的从内核态与用户态之间切换。
那么有了偏向锁,当一个线程再次访问这个同步代码或方法时,该线程只需去对象头中去判断一下是否当前线程是否持有该偏向锁就可以了。
一旦出现其它线程竞争锁资源时,偏向锁就会被撤销。偏向锁的撤销需要等待全局安全点(JVM的stop the world),暂停持有该锁的线程,同时检查该线程是否还在执行该方法,如果是,则升级锁,反之则被其它线程抢占。
轻量级锁
当有另外一个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头中的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁,如果获取成功,直接替换对象头中的线程 ID 为自己的 ID,该锁会保持偏向锁状态;如果获取锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。
轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时间的竞争。
轻量级锁也支持自旋,因此其他线程再次争抢时,如果CAS失败,将不再会进入阻塞状态,而是不断自旋。
之所以自旋更好,是因为之前说了,默认线程持有锁的时间都不会太长,如果线程被挂起阻塞可能代价会更高。
如果自旋锁重试之后抢锁依然失败,那么同步锁就会升级至重量级锁。
重量级锁
在这个状态下,未抢到锁的线程都会进入 Monitor,之后会被阻塞在WaitSet集合中,也就变成了优化之前的Synchronized锁。
JVM参数优化
偏向锁升级为轻量级锁时,会发生stop the world,如果系统常常是多线程竞争,那么禁止偏向锁也许是更好的选择,可以通过以下JVM参数进行优化:
// 关闭偏向锁(默认打开)
-XX:-UseBiasedLocking
// 设置重量级锁
-XX:+UseHeavyMonitors
轻量级锁拥有自旋锁的功能,那么如果线程持有锁的时间很长,那么竞争的线程也会常常处于自旋状态,占用系统 CPU ,增加系统开销,那么此时关闭自旋锁的优化可以更好一些:
-XX:-UseSpinning
总结
以上便是 Java 中针对 Synchronized 锁的优化,也正是因为这个优化,ConcurrentHashMap 在 JDK1.8 之后,再次采用 Synchronized 锁。如果你有什么想法,欢迎在下方留言。
有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。


Java中Synchronized的优化原理的更多相关文章
- java中synchronized与Lock的异同
本文转载自java中synchronized与Lock的异同 前言 synchronized和Lock通过互斥保障原子性,能够保护共享数据以实现线程安全,其作用包括保障原子性.可见性.有序性 常见问题 ...
- java中synchronized的用法详解
记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchron ...
- java中synchronized的使用方法与具体解释
Java语言的keyword.当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多仅仅有一个线程运行该段代码. 一.当两个并发线程訪问同一个对象object中的这个synchronized ...
- 从虚拟机指令执行的角度分析JAVA中多态的实现原理
从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...
- java中synchronized关键字分析
今天我们来分析一下java中synchronized关键字.首先来看一段java代码:(本地编译环境为mac,jdk1.8的环境) Demo.java package com.example.spri ...
- Java 中 synchronized的用法详解(四种用法)
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.本文给大家介绍java中 synchronized的用法,对本文感兴趣的朋友一起看看吧 ...
- java中 synchronized 的使用,确保异步执行某一段代码。
最近看了个有关访问网络url和下载的例子,里面有几个synchronized的地方,系统学习下,以下内容很重要,记下来. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一 ...
- JAVA 中 synchronized 详解
看到一篇关于JAVA中synchronized的用法的详解,觉得不错遂转载之..... 原文地址: http://www.cnblogs.com/GnagWang/archive/2011/02/27 ...
- Java中synchronized 修饰在static方法和非static方法的区别
[问题描述]关于Java中synchronized 用在实例方法和对象方法上面的区别 [问题分析]大家都知道,在Java中,synchronized 是用来表示同步的,我们可以synchronized ...
随机推荐
- CodeForces 989D
题意略. 思路: 可以看成是所有的云彩照常运动,而月亮在跑.只要两个云彩相交后,在分离前月亮能赶到,就算是符合题意的. 可以知道,两个相隔越远的相向运动地云彩是越有可能符合题意的,因为它们相遇所用时间 ...
- Codeforces 976D
题意略. 思路:构造题. 我们把全部的d[n]+1个点分作3部分来构造. 首先我们把原问题归约成构造d1.dn.和{d2 - d1,d3 - d1,.....,d[n-1] - d1}这样的问题,其中 ...
- 六大设计原则(C#)
为什么要有设计原则,我觉得一张图片就可以解释这一切 一.单一职责原则(SRP) 对于一个类而言,应该只有一个发生变化的原因.(单一职责不仅仅是指类) 如果一个模块需要修改,它肯定是有原因的,除此原因之 ...
- MySQL的count(*)的优化,获取千万级数据表的总行数[转]
一.前言 这个问题是今天朋友提出来的,关于查询一个1200w的数据表的总行数,用count(*)的速度一直提不上去.找了很多优化方案,最后另辟蹊径,选择了用explain来获取总行数. 二.关于cou ...
- Delphi - Windows自动计划任务与ParamStr详解
Windows自动计划任务与ParamStr详解 ParamStr函数: ParamStr(1),..ParamStr(N) ParamStr(1)代表程序入口的第一个参数,同理,ParamStr(N ...
- ZKW线段树 非递归版本的线段树
学习和参考 下面是支持区间修改和区间查询的zkw线段树模板,先记下来. #include <algorithm> #include <iterator> #include &l ...
- CodeForces 821D Okabe and City
Okabe and City 题解: 将行和列也视为一个点. 然后从普通的点走到行/列的点的话,就代表这行/列已经被点亮了. 然后将费用为0的点建上边. 注意讨论(n,m)非亮的情况下. 代码: #i ...
- 【Offer】[60] 【n个骰子的点数】
题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 把n个骰子扔在地上,所有骰子朝上一面的点数之和为S.输入n,打印出s的所有可能的值出现的概率. [牛客网刷题地址]无 思路分析 递归的方 ...
- 【Offer】[49] 【丑数】
题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 我们把只包含因子2.3和5的数称作丑数( Ugly Number).求按从小到大的顺序的第1500个丑数.例如,6.8都是丑数,但14不 ...
- SIA-GateWay之API网关安装部署指南
SIA-GATEWAY是基于SpringCloud微服务生态体系下开发的一个分布式微服务网关系统.具备简单易用.可视化.高可扩展.高可用性等特征,提供云原生.完整及成熟的接入服务解决方案.本文介绍AP ...