我们知道,从 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. Linux下手动安装MySQL5.7

    1.下载tar包,这里使用wget从官网下载 https://dev.mysql.com/downloads/mysql/ 2.将mysql安装到/usr/local/mysql下 # 解压 tar ...

  2. 小米 OJ 编程比赛 02 月常规赛 3 Logic Gatekeeper CDQ分治

    link:https://code.mi.com/problem/list/view?id=139 题意: 有一个1e6 * 1e6 大的格子,现在有两种操作:1,给一个子矩阵中的每个格子加上k.2, ...

  3. codeforces 633F The Chocolate Spree (树形dp)

    题目链接:http://codeforces.com/problemset/problem/633/F 题解:看起来很像是树形dp其实就是单纯的树上递归,就是挺难想到的. 显然要求最优解肯定是取最大的 ...

  4. 快速构建第一个Flink工程

    本文简述通过maven和gradle快速构建的Flink工程.建议安装好Flink以后构建自己的Flink项目,安装与示例运行请查看:Flink快速入门--安装与示例运行. 在安装好Flink以后,只 ...

  5. Java复习笔记(一):概念解释和运行步骤

    一.java的一些概念 JAVA语言分为三:java ee,java se,java me java se:  java标准版,java的核心功能所在 java ee: java企业级开发,包含ser ...

  6. 【Offer】[15] 【二进制中1的个数】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 思路分析 让整数和1相与,可以判断整数二进制表示中最右边一位是否为1, ...

  7. 为什么不建议使用Date,而是使用Java8新的时间和日期API?

    Java 8:新的时间和日期API 在Java 8之前,所有关于时间和日期的API都存在各种使用方面的缺陷,因此建议使用新的时间和日期API,分别从旧的时间和日期的API的缺点以及解决方法.Java ...

  8. Python(Head First)学习笔记:六

    6 定制数据对象:数据结构自定义 打包代码与数据 james2.txt: James Lee,2002-3-14,2-34,3:21,2.34,2.45,3.01,2:01,2:01,3:10,2-2 ...

  9. [1]尝试用Unity3d制作一个王者荣耀(持续更新)->选择英雄-(上)

    如果已经看过本章节:目录传送门:这是目录鸭~ 1.场景搭建: 首先我们去AssetStore逛淘宝~~~ 我淘到的是这个资源,其他好看的场景(消耗不高的都行). 然后我们导入了这个资源后,把资源根文件 ...

  10. 在JSP页面用EL表达式获取数据

    <h4>获取域对象中的值</h4><%    request.setAttribute("name", "射雕英雄传");     ...