首页 > 其他分享 >Synchronized锁

Synchronized锁

时间:2023-04-08 14:35:31浏览次数:40  
标签:Synchronized 对象 MarkWord 线程 轻量级 ID 偏向

synchronized

  • 由对象头中的 对象标志 根据锁标志位的不同而被复用 以及锁升级策略

  • 能用无锁 就不要用锁,能锁代码块 就不锁整个方法, 能用对象锁 就不用类锁. 尽可能让锁的粒度更小,以提高并发效率

  • 每个对象\类 都是一把锁, 底层是Monitor锁

    • 本质是依赖于操作系统的Mutex Lock实现,操作系统实现线程之间切换 需要从用户态到内核态, 成本高

    • Monitor监视器是由ObjectMonitor实现的, 底层是C++ 的ObjectMonitor.hpp

    • Monitor 与 java对象如何关联的

      • 1、如果一个java对象被某个线程锁住,则该java对象的mark word 字段中的LockWard指向monitor的起始地址
      • 2、monitor的owner字段存放拥有 关联对象锁的线程id
        image

用法

  • 同步块
  • 同步方法

synchronized 底层演变

  • java的线程

    • 调用start方法启动一个线程,实际上是映射到操作系统原生线程之上的, 如果要阻塞或唤醒一个线程就需要操作系统的介入, 需要在用户态和核心态之间切换, 这种切换会消耗大量的系统资源, 因为用户态和内核态都有各自专用的内存空间
  • jdk5以前,synchronized属于重量级锁,效率低下,因为监视器(monitor)是依赖于底层 操作系统的Mutex Lock(系统互斥量) 来实现的, 挂起线程和恢复线程都需要转入内核态去完成. 阻塞和唤醒一个线程需要操作系统切换CPU状态完成, 这种状态的切换需要耗费处理器时间,如果同步代码块中内容过于简单, 这种切换的时间可能比用户代码执行时间还长, 时间成本高

  • Mutex Lock

    • Monitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要耗费很多的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作
  • jdk6之后, 为了减少获得锁和释放锁所带来的性能消耗, 引入了轻量级锁和偏向锁, 需要有个逐步升级的过程,别一开始就直接到重量级锁

锁介绍

  • 无锁态

    • 只有一个线程,无竞争
  • 偏向锁

    • 当一段同步代码一直被同一个线程多次访问,由于只有一个线程, 那么该线程在后续访问时便会自动获取锁

    • 由来

      • 实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程
    • 理论

      • 在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。
      • 如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
      • 假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的
    • 一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入Monitor去竞争对象了。

    • 偏向锁的相关参数

      • 偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟, 所以需要添加参数-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动
      • 开启: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
      • 关闭: -XX:-UseBiasedLocking
  • 轻量级锁

    • 本质就是自旋锁, 有线程来参与锁的竞争,但是获取锁的冲突时间极短

    • 理论

      • (1). 轻量级锁是为了在线程近乎交替执行同步块时提高性能
      • (2). 主要目的: 在没有多线程竞争的前提下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗,说白了先自旋再阻塞
      • (3). 升级时机:当关闭偏向锁功能或多线程竞争偏向锁会导致偏向锁升级为轻量级锁
      • (4). 假如线程A已经拿到锁,这时线程B又来抢该对象的锁,由于该对象的锁已经被线程A拿到,当前该锁已是偏向锁了。
        而线程B在争抢时发现对象头Mark Word中的线程ID不是线程B自己的线程ID(而是线程A),那线程B就会进行CAS操作希望能获得锁。
        此时线程B操作中有两种情况
        • 如果锁获取成功,直接替换Mark Word中的线程ID为B自己的ID(A → B),重新偏向于其他线程(即将偏向锁交给其他线程,相当于当前线程"被"释放了锁),该锁会保持偏向锁状态,A线程Over,B线程上位
          image
        • 如果锁获取失败,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程B会进入自旋等待获得该轻量级锁。
          image
  • 重量级锁

    • monitor实现, 进入之前monitorenter ,之后 monitor
  • 比较

  • Displaced MarkWord: JVM会为每个线程在当前线程的栈桢中创建用于存储锁记录的空间

说明 无锁 偏向锁 轻量级锁 重量级锁
加锁 - 锁标志位 001 将当前线程ID记录到MarkWord、锁标志位 101 一个线程获取锁 会把锁的markWord复制到自己的Displaced MarkWord,然后其他线程尝试用CAS将锁的MarkWord替换为指向锁记录的指针, 成功则获取锁,失败则自旋 锁标志位 000 -
解锁 MarkWord 当前线程ID清除 释放锁时,CAS将Displaced MarkWord的内容复制回MarkWord
markWord存储 指向的是线程ID 线程栈中Lock Record的指针 堆中的monitor对象的指针

锁升级过程

  • 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
    image

  • 轻量锁 -> 重量级

    • JDK6之前 CAS次数达到10次, 就会升级重量级锁
    • JDK6之后 自适应次数
      • 线程如果自旋成功了,那下次自旋的最大次数会增加
      • 很少成功,下次会减少自旋的次数

锁消除

  • 从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用
  • 一个线程自己加锁,没必要

锁粗化

  • 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能

拓展信息

  • jdk15已经去掉了偏向锁, 原因是因为维护成本较高, 费力不讨好
    • 如果在jdk15还想用到偏向锁,可以手动设置jvm参数: -XX:+UseBiased
  • 锁升级后与hashcode的关系
    • 无锁状态下, Mark Word中可以存储对象的hash code值,当对象的hashcode方法被第一次调用时,JVM会生成对应的hashcode值 存储到Mark Word中

    • 对于偏向锁,在线程获取偏向锁时, 会用ThreadId 和 epoch 覆盖hash code所在的位置. 如果一个对象的hashCode方法已经被调用过了,这个对象不能被设置偏向锁.

      • 如果可以, 那MarkWord中的hashcode必然会被覆盖,就会导致 两次hashcode方法不一样
    • 升级成了轻量级锁,JVM会在当前线程的栈桢中创建一个锁记录空间,用户存储锁对象的MarkWord拷贝, 该拷贝中是含有hashcode的

    • 升级成重量级锁, MarkWord保存的是重量级锁指针(Monitor对象指针), ObjectMonitor类中有字段记录非加锁状态下的MarkWord, 锁记录释放后会将信息写会对象头

    • 特殊场景

      • 当一个对象已经计算过hash code, 它就无法进入偏向锁状态, 会跳过偏向锁,直接进入轻量级锁
      • 偏向锁过程中遇到一致性hash请求, 立马撤销偏向模式, 膨胀为重量级锁

标签:Synchronized,对象,MarkWord,线程,轻量级,ID,偏向
From: https://www.cnblogs.com/liyong888/p/17298502.html

相关文章

  • JUC并发编程基础篇第三章之Synchronized八锁案例[理解锁的对象]
    目录1、总结2、Java8锁案例1打印的方法都有synchronized修饰,先调用email,后调用Sms;输出顺序?案例2如果在发送email的方法,加入了暂定3s中的操作,打印顺序?案例3增加一个普通的方法hello,此时b线程调用hello,先打印email还是hello?案例4有两部手机,先打印邮件还是短信案......
  • Synchronized实现原理,你知道多少?
    1.synchronized的作用是什么 synchronized也叫作同步锁,解决的是多个线程之间对资源的访问一致性。换句话说,就是保证在同一时刻,被synchronized修饰的方法或代码块只有一个线程在执行,其他线程必须等待,解决并发安全问题。其可以支持原子性、可见性和有序性。三大特性的说明2.sync......
  • 【Java 并发】【synchronized】【一】synchronized底层是怎么通过monitor进行加锁的
    1 前言之前我们说过对象头的信息,这节我们就来看看synchronized是怎么通过monitor进行重量级加锁。2 内容回顾我们先来回顾下MarkWord的内容:当MarkWord的最后两位的锁标志位是10的时候,MarkWord这哥们说自己处于重量级锁的模式,重量级加锁不是它的责任,是monitor的责任。......
  • synchronized
    基本使用Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:原子性:确保线程互斥的访问同步代码;可见性:保证共享......
  • synchronized锁升级底层原理
    今天我们来聊聊Synchronized里面的各种锁:偏向锁、轻量级锁、重量级锁,以及三个锁之间是如何进行锁膨胀的。先来一张图来总结提前了解知识锁的升级过程锁的状态总共有四种:无......
  • 为什么 wait/notify 必须与 synchronized 一起使用
    注:本文转自:https://mp.weixin.qq.com/s/ZbB_4vYg6aNMDyy6KnjSnA为什么javawait/notify必须与synchronized一起使用这个问题就是书本上没怎么讲解,就是告诉我们这样处......
  • synchronized 和 Lock 的区别
    Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock......
  • 使用synchronized对并发性的影响
    1前言非静态方法的同步锁是当前对象(this)(对象锁)静态方法的同步锁是当前类的字节码(类锁)不同的锁之间能并发2同一对象内本节主类与资源类如下:classResorce{//资源......
  • Java synchronized的实现原理
    通常在多线程执行的过程中,我们需要考虑一些线程安全的问题,而线程安全问题中最常用的解决策略之一就是“锁”。加锁的本质,就是为了解决在多线程场景中对于共享数据访问的......
  • 对并发熟悉吗?说说synchronized及实现原理
    synchronized的基本使用synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。作用主要有三个:确保线程互斥的访问同步代码保证共享变量的修改能......