• 深入理解java:2.2. 同步锁Synchronized及其实现原理


    同步的基本思想

    为了保证共享数据在同一时刻只被一个线程使用,我们有一种很简单的实现思想,就是 

    在共享数据里保存一个锁 ,当没有线程访问时,锁是空的。

    当有第一个线程访问时,就 在锁里保存这个线程的标识 并允许这个线程访问共享数据。

    在当前线程释放共享数据之前,如果再有其他线程想要访问共享数据,就要 等待锁释放 。

     

    • 在共享数据里保存一个锁
    • 在锁里保存这个线程的标识
    • 其他线程访问已加锁共享数据要等待锁释放

    Jvm同步的实现

    jvm中有以下三种锁(由上到下越来越“重量级”):

    1. 偏向锁
    2. 轻量级锁
    3. 重量级锁

    重量级锁

    Synchronized 原理

    我们直接参考JVM规范中描述:每个对象有一个监视器锁(monitor)

    当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

    2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

    3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

    Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,

    这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

    Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。

    但是监视器锁本质又是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。

    因此,这种依赖于操作系统互斥锁(Mutex Lock)所实现的锁我们称之为“重量级锁”。

    轻量级锁 

      锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。

       JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。

     

    轻量级锁的核心思想就是“被加锁的代码不会发生并发,如果发生并发,那就膨胀成重量级锁(膨胀指的锁的重量级上升,一旦升级,就不会降级了)”。

       轻量级锁依赖了一种叫做CAS(compare and swap)的操作。

    术语定义

    术语 英文 说明
    CAS Compare and Swap

    比较并设置。

    用于在硬件层面上提供原子性操作

    在 Intel 处理器中,比较并交换通过指令cmpxchg实现。比较是否和给定的数值一致,如果一致则修改,不一致则不修改。

    偏向锁

    根据轻量级锁的实现,我们知道虽然轻量级锁不支持“并发”,遇到“并发”就要膨胀为重量级锁,但是轻量级锁可以支持多个线程以串行的方式访问同一个加锁对象。

    比如A线程可以先获取对象o的轻量锁,然后A释放了轻量锁,这个时候B线程来获取o的轻量锁,是可以成功获取得,以这种方式可以一直串行下去。

     

    之所以能实现这种串行,是因为有一个释放锁的动作。那么假设有一个加锁的java方法,这个方法在运行的时候其实从始至终只有一个线程在调用,但是每次调用完却也要释放锁,下次调用还要重新获得锁。

    那么我们能不能做一个假设:“假设加锁的代码从始至终就只有一个线程在调用,如果发现有多于一个线程调用,再膨胀成轻量级锁也不迟”。这个假设,就是偏向锁的核心思想。

       偏向锁依赖了一种叫做CAS(compare and swap)的操作。

    总结 

      本文重点介绍了JDk中采用轻量级锁和偏向锁等对Synchronized的优化,

       但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程,这个时候就需要通过-XX:-UseBiasedLocking来禁用偏向锁。下面是这几种锁的对比:

    优点

    缺点

    适用场景

    偏向锁

    加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。

    如果线程间存在锁竞争,会带来额外的锁撤销的消耗

    适用于只有一个线程访问同步块场景。

    轻量级锁

    竞争的线程不会阻塞,提高了程序的响应速度。

    如果始终得不到锁竞争的线程使用自旋会消耗CPU

    追求响应时间

    同步块执行速度非常快。

    重量级锁

    线程竞争不使用自旋,不会消耗CPU。

    线程阻塞,响应时间缓慢。

    追求吞吐量

    同步块执行速度较长。

  • 相关阅读:
    Visual Studio 2008 菜单:工具+选项+文本编辑器+HTML+格式,选中“键入时插入属性值引号”
    itemarray的意思
    SQL技巧大全
    IIS调用com组件的权限问题
    网站快速备案法(1小时)
    ASP.NET 2.0中WEB应用程序的部署
    c#中MessageBox的使用
    推荐一款DataGridView的打印解决方案
    使用C#格式化字符串
    关于MSSQL导入导出时主键与约束丢失的问题解决
  • 原文地址:https://www.cnblogs.com/my376908915/p/6757833.html
Copyright © 2020-2023  润新知