我们知道,从 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 锁。如果你有什么想法,欢迎在下方留言。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

Java中Synchronized的优化原理的更多相关文章

  1. java中synchronized与Lock的异同

    本文转载自java中synchronized与Lock的异同 前言 synchronized和Lock通过互斥保障原子性,能够保护共享数据以实现线程安全,其作用包括保障原子性.可见性.有序性 常见问题 ...

  2. java中synchronized的用法详解

    记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchron ...

  3. java中synchronized的使用方法与具体解释

    Java语言的keyword.当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多仅仅有一个线程运行该段代码. 一.当两个并发线程訪问同一个对象object中的这个synchronized ...

  4. 从虚拟机指令执行的角度分析JAVA中多态的实现原理

    从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...

  5. java中synchronized关键字分析

    今天我们来分析一下java中synchronized关键字.首先来看一段java代码:(本地编译环境为mac,jdk1.8的环境) Demo.java package com.example.spri ...

  6. Java 中 synchronized的用法详解(四种用法)

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.本文给大家介绍java中 synchronized的用法,对本文感兴趣的朋友一起看看吧 ...

  7. java中 synchronized 的使用,确保异步执行某一段代码。

    最近看了个有关访问网络url和下载的例子,里面有几个synchronized的地方,系统学习下,以下内容很重要,记下来. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一 ...

  8. JAVA 中 synchronized 详解

    看到一篇关于JAVA中synchronized的用法的详解,觉得不错遂转载之..... 原文地址: http://www.cnblogs.com/GnagWang/archive/2011/02/27 ...

  9. Java中synchronized 修饰在static方法和非static方法的区别

    [问题描述]关于Java中synchronized 用在实例方法和对象方法上面的区别 [问题分析]大家都知道,在Java中,synchronized 是用来表示同步的,我们可以synchronized ...

随机推荐

  1. 设计模式(C#)——06桥接模式

    推荐阅读:  我的CSDN  我的博客园  QQ群:704621321       在早先,几乎每个手机的充电器接口都是不同的.每个型号的手机都有一个充电器,此时我们把充电器作为一个抽象类,抽象类中提 ...

  2. Unity Editor已停止工作

    在更换系统之后,可能会出现打开刚安装好的Unity,显示Unity Editor已停止工作,这时候我们考虑是系统win7的问题.可以在原系统上升级,也可以重新安装,升级.文中所涉及到的软件,可在右侧加 ...

  3. Leetcode之深度优先搜索(DFS)专题-301. 删除无效的括号(Remove Invalid Parentheses)

    Leetcode之深度优先搜索(DFS)专题-301. 删除无效的括号(Remove Invalid Parentheses) 删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果. 说明 ...

  4. 【原创】Linux cpufreq framework

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  5. c3p0,dbcp与druid 三大连接池的区别[转]

    说到druid,这个是在开源中国开源项目中看到的,说是比较好的数据连接池.于是乎就看看.扯淡就到这. 下面就讲讲用的比较多的数据库连接池.(其实我最先接触的是dbcp这个) 1)DBCP DBCP是一 ...

  6. Elasticsearch(5)--- 基本命令(集群相关命令、索引CRUD命令、文档CRUD命令)

    Elasticsearch(5)--- 基本命令 这篇博客的命令分为ES集群相关命令,索引CRUD命令,文档CRUD命令.这里不包括Query查询命令,它单独写一篇博客. 一.ES集群相关命令 ES集 ...

  7. JavaScript get set方法 ES5/ES6写法

    网上鲜有get和set的方法的实例,在这边再mark一下. get和set我个人理解本身只是一个语法糖,它定义的属性相当于“存储器属性” 为内部属性提供了一个方便习惯的读/写方式 ES5写法 func ...

  8. 分层图 单调决策性DP

    easy 写法. #include<bits/stdc++.h> using namespace std; #define Fopen freopen("_in.txt" ...

  9. [NOI2009]诗人小G 四边形优化DP

    题目传送门 f[i] = min(f[j] + val(i,j); 其中val(i,j) 满足 四边形dp策略. 代码: #include<bits/stdc++.h> using nam ...

  10. hdu 4614 Vases and Flowers(线段树)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4614 题意: 给你N个花瓶,编号是0  到 N - 1 ,初始状态花瓶是空的,每个花瓶最多插一朵花. ...