zoukankan      html  css  js  c++  java
  • 【学习底层原理系列】Java底层-synchronized锁-2偏向锁篇

    上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对synchronized关键字做了介绍,

    快速跳转:https://www.cnblogs.com/xyang/p/11631866.html

    本文将从底层实现的各个“组件”着手,详细拆解其工作原理。

    本文会分为以下2节内容:

      第一节:介绍MarkWord和LockRecord两种数据结构,该知识点是理解synchronized关键字底层原理的关键。

      第二节:分析偏向锁加锁解锁时机和过程

    一.先来了解两种数据结构,你应该了解这些知识点

    1.MarkWord:在锁的使用过程中会对锁对象作出相应的操作

     在HotSpot虚拟机中,Java对象在内存中存储的布局,分为三个部分:对象头,实例数据,对齐填充。

    本文重点关注对象头。

    对象头又划分为2或3部分,具体包括:

    1. MarkWord(后文简称MW,后续详细介绍)
    2. 类型指针:指向这个对象所属的类的元数据(klass)的指针
    3. 最后这一部分比较特殊,只有在对象是Java数组时才会存在,记录的是数组的长度。为什么要存在这个记录呢?我们知道,在普通Java对象中,我们可以通过读取对象所属类的元数据,计算出对象的大小。而数组是无法做到的,于是借助这块区域来记录。

    本文重点关注MW区域

    MW是一块固定大小内存区域,在32位虚拟机中是32个bit,对应的,64位虚拟机中是64个bit。本文以32位虚拟机为例分析。

    我们从直观上理解,所谓的头信息,一般都是用来记载一些不易变的信息,例如在http请求头中的各种头信息。在对象头中也是如此,例如hashcode。在JVM虚拟机中为了解决存储空间开销,对象头的MW大小已经固定。那么,要存储的信息有比较多,包括且不限于:锁标志位、GC信息、锁相关信息,总大小远远超出32bit,怎么办呢?

    共享存储区域,在不同的时刻,根据需求存储需要的信息。

    请参考下图:

    锁类型

    25bit

    4bit

    1bit

    2bit

     

    23bit

    2bit

    是否偏向锁

    锁标志位

    无锁

    对象hashcode

    分代年龄

    0

    01

    偏向锁

    线程ID

    epoch

    分代年龄

    1

    01

    轻量级锁

    指向栈中锁记录的指针

    00

    重量级锁

    指向互斥量

    10

    GC标记

    11

    说明:两个标志位最多只能标识4个状态,那么剩下一个怎么办?共享。无锁和偏向锁共享01状态,他们两个的区分

    2.LockRecord:

    在当前线程的栈中申请LR(LockRecord简称,下同),主要包含两部分,第一步部分可以用于存放MW的副本;第二部分obj,用于指向锁对象。

     上述两者的关系用下图表示:

    二.偏向锁怎么工作

    在对象创建的时候,MW会有一个初始态,要么是无锁态,要么是初始偏向锁态(ThreadId、epoch值都为初始值0)。程序员的世界不存在二义性,最终总会选一个,选择的依据是虚拟机的配置参数,在JDK1.6以后,默认是开启的,如果要禁用掉:-XX:-UseBiasedLocking。

    什么时候需要禁用呢?如果能确认程序在大多数情况下,都存在多线程竞争,那么就可以禁用掉偏向锁。没必要每次都走一遍偏向锁->轻量级锁->重量级锁的完整升级流程。

    1.先放一张图,直观的描述偏向锁的加锁、解锁、撤销基本流程

    2.加锁过程

     步骤一:

    1. LR记录赋值:在当前线程的栈中,申请一个LR,把obj指向锁对象

    步骤二:如图中所示,线程T1,执行到同步代码,尝试加偏向锁,【判断三要素:标志位,ThreadId,epoch】:

    1. 先用标志位判断是否支持偏向锁。锁对象的对象头MW区域后3个bit位的值是101。特别需要注意:如果是001,是无锁状态,代表偏向锁不可用,会走加轻量级锁流程。
    2. 再用ThreadId值,决定加锁还是重入还是竞争
      1. 0值加锁。如果ThreadId=0,代表无任何线程持有该对象的偏向锁,可以执行加锁操作,进入加锁流程;
      2. 非0值,
        1. 且为当前线程id,重入。如果ThreadId!=0,就判断其值是否是当前线程的ID,分两种情况:如果是,直接锁重入,不再重复加锁。
        2. 不为当前线程id,加锁或竞争。非0值,说明是其他线程(图中T2)已获得了同步锁。对象所属Class里也会维护一个epoch值,这里我们简称为cEpoch,比较两者:
          1. epoch小于类的cEpoch值,加锁。如果epoch<cEpoch,说明发生过批量重偏向,当前锁对象已被“释放”了。此时进行“重偏向”(里说的释放并非真正意义的释放,而是隐含着一层意思:当前线程已经执行完同步块,且在某次重偏向操作中,也检测到这一点,不再维护epoch的最新值,这样新的线程认为此时该偏向锁,可以加锁,直接CAS修改ThreadId即可)
          2. epoch等于类的cEpoch值,竞争锁。

        标准的可加锁状态MW内容如下图所示:

        

    锁类型

    25bit

     

    4bit

    1bit

    2bit

     

    23bit

    2bit

     

    是否偏向锁

    锁标志位

    偏向锁

    ThreadId==0

    epoch==n

    分代年龄

    1

    01

        或者

    锁类型

    25bit

     

    4bit

    1bit

    2bit

     

    23bit

    2bit

     

    是否偏向锁

    锁标志位

    偏向锁

    ThreadId!=0

    epoch==n(小于cepoch)

    分代年龄

    1

    01

    第二步:通过CAS原子操作,把T1的ThreadId写入MW。执行结果有两种情况:

    1. 写入成功,获得偏向锁,进入同步代码块执行同步逻辑。
    2. 写入失败,表明在第一步判断和CAS操作之间,有其他线程已获得了锁。走锁竞争逻辑。

    2.解锁过程

    当前线程执行完同步代码块后,进行解锁,解锁操作比较简单,仅仅将栈中的最近一条LR中的obj赋值为null。这里需要注意,MW中的threadId并不会做修改。

    3.锁竞争处理流程

      T1尝试加锁,

      如果存在锁竞争情况,持有锁的线程T2并不会在发现竞争的第一时间就直接撤销锁,或者升级锁,而是执行到安全点后再处理。

      1.   此时如果当前线程已执行完同步块代码线程已不存活,将会撤销锁至无锁状态,然后进入锁升级逻辑。
      2.   否则,将会走锁升级流程,升级为轻量级锁,且升级完后T2继续持有轻量级锁,继续执行同步代码。

      ps:怎么判断是否还在执行同步代码呢?遍历栈中的RL,如果都为null,代表锁已全部释放。

    4.批量重偏向和批量撤销

    有这样一种场景:如果我们预判竞争不多,大部分情况下是单一线程执行同步块,开启了偏向锁。但是在实际使用环境中,出现了大量的竞争,这时候怎么办呢?停机重新配置参数?恐怕不是最好的方案。如果是我们来设计这个这个Synchronized锁,肯定也会做一些兜底策略。比如这样来做,当某一事件发生了N次,那么就更改一下处理策略?

    是的,基本思想差不多,只不过更完善,暂时留一个悬念,在下次揭晓。

  • 相关阅读:
    Elementary Methods in Number Theory Exercise 1.3.13
    Elementary Methods in Number Theory Exercise 1.3.17, 1.3.18, 1.3.19, 1.3.20, 1.3.21
    数论概论(Joseph H.Silverman) 习题 5.3,Elementary methods in number theory exercise 1.3.23
    Elementary Methods in Number Theory Exercise 1.2.31
    数论概论(Joseph H.Silverman) 习题 5.3,Elementary methods in number theory exercise 1.3.23
    Elementary Methods in Number Theory Exercise 1.3.13
    Elementary Methods in Number Theory Exercise 1.3.17, 1.3.18, 1.3.19, 1.3.20, 1.3.21
    Elementary Methods in Number Theory Exercise 1.2.31
    Elementary Methods in Number Theory Exercise 1.2.26 The Heisenberg group
    4__面向对象的PHP之作用域
  • 原文地址:https://www.cnblogs.com/xyang/p/11698549.html
Copyright © 2011-2022 走看看