精华内容
下载资源
问答
  • 其实在JDK1.5以前的早期版本,还没有那么细粒度完善的锁机制,基本上就一个synchronized打遍天下,但是从JDK1.6之后Oracle对Java锁进行了很大的改动,也就出现了偏向锁/轻量级锁机制和锁的升级/降级机制 偏向锁和轻...

    最近因为工作关系遇到了很多Java并发编程的问题,然后恶补了一下,现在就来说说Java目前的锁实现原理

    其实在JDK1.5以前的早期版本,还没有那么细粒度完善的锁机制,基本上就一个synchronized打遍天下,但是从JDK1.6之后Oracle对Java锁进行了很大的改动,也就出现了偏向锁/轻量级锁机制和锁的升级/降级机制

    偏向锁和轻量级锁都属于乐观锁,偏向锁指的是没有其他线程竞争资源,只有一个线程在执行同步块代码,这个时候在会使用CAS操作在对象头部信息中写进拿到锁的那个线程ID/锁级别等信息,偏向锁的使用场景主要是为了提高执行性能,因为在大多数情况下并不存在频繁的多个线程对于同一个代码块进行竞争,那么就没必要同一个线程执行还执行拿锁/释放锁这种耗时操作,大致流程下图:

    而当第一个拿到偏向锁的线程执行时,遇到有新的进程在询问统一代码块的锁时就有可能会升级成轻量级锁,为什么说是有可能呢?因为偏向锁不会自动释放,此时第2个线程询问锁时会出现2种情况:

    1. 第一个线程已经执行完毕,那么CAS操作将Mark Word设置为Null,第二个线程获取偏向锁,此时不会升级成轻量级锁
    2. 第一个线程未执行完毕,此时第二个线程获取锁失败,那么会进行自旋,当自旋达到一定次数后,就会升级成轻量级锁

    轻量级锁流程见下图:

    同理,当需要获取锁的线程越来越多并且自旋达到一定数目后,就会升级成重量级锁,重量级锁也就是悲观锁,完全阻塞状态,必须等待线程执行完成释放锁之后排队线程才能挨个执行,这个就是锁的升级过程

     

     

    展开全文
  • 利用synchronized实现同步的基础:Java中的每一个对象都可以作为。具体表现 为以下3种形式。 · 对于普通同步方法,是当前实例对象。 · 对于静态同步方法,是当前类的Class对象。 对于同步方法块...

    在JDK1.6之后synchronized 的效率已经和JUC中的ReentenerLock效率相差无几,在深入理解java虚拟机中也有提到。那么jvm团队做了哪些优化呢?

    1.基础

    利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现 为以下3种形式。 ·
    对于普通同步方法,锁是当前实例对象。 ·
    对于静态同步方法,锁是当前类的Class对象。
    对于同步方法块,锁是Synchonized括号里配置的对象。

    2.自旋锁和自适应自旋锁

    为了让线程等 待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。 自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning 参数来开启,在JDK 1.6中就已经改为默认开启了。自旋等待不能代替阻塞,且先不说对处 理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的, 因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间 很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性 能上的浪费。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然 没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次,用户可 以使用参数-XX:PreBlockSpin来更改。 在JDK 1.6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前 一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等 待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有 可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。另外,如果 对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避 免浪费处理器资源。有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对 程序锁的状况预测就会越来越准确,虚拟机就会变得越来越“聪明”了。
    ---------- 深入理解java虚拟机
    这里是引用

    2.锁粗化和锁消除

    锁消除是说比如在一个线程运行栈中的变量加锁,虚拟机会自动去掉这个锁,你可能会问,谁会在这个地方加锁呢,这可能是你无意识的,比如String的连接操作在底层会被改成StringBuider的连续append操作,而这些操作时加锁的。
    在这里插入图片描述
    锁粗化简单的说就是在连续对一个对象进行加锁的时候,虚拟机会将锁的力度扩大到这几个连续加锁的代码外,看起来就是只加一个锁。同样是StringBuilder的连续append方法。

    Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在 Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状 态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏 向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高 获得锁和释放锁的效率,下文会详细分析

    1.偏向锁

    HotSpot 的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

    2.轻量级锁

    轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;

    3.重量级锁

    内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。

    基础2 对象头

    在 HotSpot 虚拟机中,对象在内存中存储布局分为 3 块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
    而对象头根据锁状态的不同表示的也有所不同
    详细地址https://www.cnblogs.com/zhengbin/p/6490953.html
    在这里插入图片描述
    当一个对象被创建后 根据虚拟机的配置,会在这个对象对象头中设置改对象是否可偏向,1表示可以偏向 0表示不可偏
    在这里插入图片描述

    具体见下图
    在这里插入图片描述

    展开全文
  • Java中偏向锁,轻量级锁, 重量级锁解析

    万次阅读 多人点赞 2018-08-13 18:39:49
    参考文章 聊聊并发(二)Java SE1.6的Synchronized Lock Lock Lock: Enter! 5 Things You Didn’t Know About Synchronization in Java and Scala ...Java Java 主要2种加锁机制: synchr...

    参考文章

    Java 中的锁

    在 Java 中主要2种加锁机制:

    • synchronized 关键字
    • java.util.concurrent.LockLock是一个接口,ReentrantLock是该接口一个很常用的实现)

    这两种机制的底层原理存在一定的差别

    • synchronized 关键字通过一对字节码指令 monitorenter/monitorexit 实现, 这对指令被 JVM 规范所描述。
    • java.util.concurrent.Lock 通过 Java 代码搭配sun.misc.Unsafe 中的本地调用实现的

    一些先修知识

    先修知识 1: Java 对象头

    • 字宽(Word): 内存大小的单位概念, 对于 32 位处理器 1 Word = 4 Bytes, 64 位处理器 1 Word = 8 Bytes
    • 每一个 Java 对象都至少占用 2 个字宽的内存(数组类型占用3个字宽)。
      • 第一个字宽也被称为对象头Mark Word。 对象头包含了多种不同的信息, 其中就包含对象锁相关的信息。
      • 第二个字宽是指向定义该对象类信息(class metadata)的指针
    • 非数组类型的对象头的结构如下图
      在这里插入图片描述
    • 说明:
      • MarkWord 中包含对象 hashCode 的那种无锁状态是偏向机制被禁用时, 分配出来的无锁对象MarkWord 起始状态
      • 偏向机制被启用时,分配出来的对象状态是 ThreadId|Epoch|age|1|01, ThreadId 为空时标识对象尚未偏向于任何一个线程, ThreadId 不为空时, 对象既可能处于偏向特定线程的状态, 也有可能处于已经被特定线程占用完毕释放的状态, 需结合 Epoch 和其他信息判断对象是否允许再偏向(rebias)。

    下面的图片来自参考论文 Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing , 可以与上面的表格进行比对参照, 更为清晰, 可以看出来, 标志位(tag bits)可以直接确定唯一的一种锁状态

    在这里插入图片描述

    先修知识 2: CAS 指令

    • CAS (Compare And Swap) 指令是一个CPU层级的原子性操作指令。 在 Intel 处理器中, 其汇编指令为 cmpxchg。
    • 该指令概念上存在 3 个参数, 第一个参数【目标地址】, 第二个参数【值1】, 第三个参数【值2】, 指令会比较【目标地址存储的内容】和 【值1】 是否一致, 如果一致, 则将【值 2】 填写到【目标地址】, 其语义可以用如下的伪代码表示。
    function cas(p , old , new ) returns bool {
        if *p ≠ old { // *p 表示指针p所指向的内存地址
            return false
        }
        *p ← new
        return true
    }
    
    • 注意: 该指令是是原子性的, 也就是说 CPU 执行该指令时, 是不会被中断执行其他指令的

    先修知识 3: “CAS”实现的"无锁"算法常见误区

    • 误区一: 通过简单应用 “比较后再赋值” 的操作即可轻松实现很多无锁算法
      • CAS 指令的一个不可忽略的特征是原子性。 在 CPU 层面, CAS 指令的执行是有原子性语义保证的, 如果 CAS 操作放在应用层面来实现, 则需要我们自行保证其原子性。 否则就会发生如下描述的问题:
    // 下列的函数如果不是线程互斥的, 是错误的 CAS 实现
    function cas( p , old , new) returns bool {
        if *p ≠ old { // 此处的比较操作进行时, 可以同时有多个线程通过该判断
            return false
        }
        *p ← new // 多个线程的赋值操作会相互覆盖, 造成程序逻辑的错误
        return true
    }
    
    • 误区二: CAS 操作的 ABA 问题
      • 大部分网络博文对 ABA 问题的常见描述是: 应用 CAS 操作时, 目标地址的值刚开始为 A, 工作线程/进程 读取后, 进行了一系列运算, 计算得出了新值 C, 在此期间, 目标地址的值被其他线程已经进行了不止一次修改, 其值已经从 A 被改为 B , 又改回 A, 此时便会发生同步问题。
      • 上面的描述是其实是错误的, 思考一下就会发现, 如果工作线程的操作目的是将目标地址的值从 A 改为 C, 那么即便在这期间目标地址的值经过了其他线程或进程的多次修改, 其语义依旧是正确的。
      • 例如目前要将某银行账号的余额扣除 50, 通过 CAS 保证同步 :
        • 首先读取原有余额为 100 ,
        • 计算余额应该赋值为 100 - 50 = 50
        • 此时该线程被挂起, 该账户同时又发生了转入 150 和转出 150 的操作, 余额经历了 100 -》250 -》100 的变动
        • 线程被唤醒, 进行 CAS 赋值操作 cas(p, 100, 50) , 正常得以执行。
        • 该账户的余额依旧是正确的
      • 通过上述例子就可以发现, ABA 的问题并不在于多次修改。 查阅一下 CAS 的 wiki 解释, 就会发现, ABA 真正的问题是, 假如目标地址的内容被多次修改以后, 虽然从二进制上来看是依旧是 A, 但是其语义已经不是 A 。例如, 发生了整数溢出, 内存回收等等。

    先修知识 4: 栈帧(Stack Frame) 的概念

    • 这个概念涉及的内容较多, 不便于展开叙述。 从理解下文的角度上来讲, 需要知道, 每个线程都有自己独立的内存空间, 栈帧就是其中的一部分。里面可以存储仅属于该线程的一些信息。
    • 需要深入了解的同学, 需要自行查阅 栈帧 相关的概念

    先修知识 5: 轻量级加锁的过程

    轻量级加锁的过程在参考文章一中有较为的描述以及配图, 这里直接将其摘抄过来, 做轻微整理和调整

    • (1)在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。

    • (2)拷贝对象头中的Mark Word复制到锁记录中。这时候线程堆栈与对象头的状态如图2.1所示

    • 图 2.1
      图2.1

    • (3)拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。

    • (4)如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图2.2所示。

    • 图2.2
      在这里插入图片描述

    • 5)如果这个更新操作失败了,说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁

    先修知识 6: 重量级加锁的过程

    • 轻量级锁在向重量级锁膨胀的过程中, 一个操作系统的互斥量(mutex)和条件变量( condition variable )会和这个被锁的对象关联起来。
    • 具体而言, 在锁膨胀时, 被锁对象的 markword 会被通过 CAS 操作尝试更新为一个数据结构的指针, 这个数据结构中进一步包含了指向操作系统互斥量(mutex) 和 条件变量(condition variable) 的指针

    synchronized 关键字之锁的升级(偏向锁->轻量级锁->重量级锁)

    前面提到过, synchronized 代码块是由一对 monitorenter/moniterexit 字节码指令实现, monitor 是其同步实现的基础, Java SE1.6 为了改善性能, 使得 JVM 会根据竞争情况, 使用如下 3 种不同的锁机制

    • 偏向锁(Biased Lock )
    • 轻量级锁( Lightweight Lock)
    • 重量级锁(Heavyweight Lock)

    上述这三种机制的切换是根据竞争激烈程度进行的, 在几乎无竞争的条件下, 会使用偏向锁, 在轻度竞争的条件下, 会由偏向锁升级为轻量级锁, 在重度竞争的情况下, 会升级到重量级锁。

    注意 JVM 提供了关闭偏向锁的机制, JVM 启动命令指定如下参数即可

    -XX:-UseBiasedLocking
    

    下图展现了一个对象在创建(allocate) 后, 根据偏斜锁机制是否打开, 对象 MarkWord 状态以不同方式转换的过程

    这里写图片描述
    上图在参考文章一中的中文翻译对照图如下

    在这里插入图片描述

    无锁 -> 偏向锁

    在这里插入图片描述

    从上图可以看到 , 偏向锁的获取方式是将对象头的 MarkWord 部分中, 标记上线程ID, 以表示哪一个线程获得了偏向锁。 具体的赋值逻辑如下:

    • 首先读取目标对象的 MarkWord, 判断是否处于可偏向的状态(如下图)
      这里写图片描述
      下面是 Open Jdk/ JDK 8 源码 中检测一个对象是否处于可偏向状态的源码
      // Indicates that the mark has the bias bit set but that it has not
      // yet been biased toward a particular thread
      bool is_biased_anonymously() const {
        return (has_bias_pattern() && (biased_locker() == NULL));
      }
    

    应评论区一位朋友的提问, 进一步摘抄一下markOop.hpp中的方法定义

    // Biased Locking accessors.
      // These must be checked by all code which calls into the
      // ObjectSynchronizer and other code. The biasing is not understood
      // by the lower-level CAS-based locking code, although the runtime
      // fixes up biased locks to be compatible with it when a bias is
      // revoked.
      bool has_bias_pattern() const {
        return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);
      }
      JavaThread* biased_locker() const {
        assert(has_bias_pattern(), "should not call this otherwise");
        return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));
      }
    
    • has_bias_pattern() 返回 true 时代表 markword 的可偏向标志 bit 位为 1 ,且对象头末尾标志为 01。

    • biased_locker() == NULL 返回 true 时代表对象 Mark Word 中 bit field 域存储的 Thread Id 为空。

    • 如果为可偏向状态, 则尝试用 CAS 操作, 将自己的线程 ID 写入MarkWord

      • 如果 CAS 操作成功(状态转变为下图), 则认为已经获取到该对象的偏向锁, 执行同步块代码 。 注意, age 后面的标志位中的值并没有变化, 这点之后会用到
      • 补充: 一个线程在执行完同步代码块以后, 并不会尝试将 MarkWord 中的 thread ID 赋回原值 。这样做的好处是: 如果该线程需要再次对这个对象加锁,而这个对象之前一直没有被其他线程尝试获取过锁,依旧停留在可偏向的状态下, 即可在不修改对象头的情况下, 直接认为偏向成功。
        这里写图片描述
      • 如果 CAS 操作失败, 则说明, 有另外一个线程 Thread B 抢先获取了偏向锁。 这种状态说明该对象的竞争比较激烈, 此时需要撤销 Thread B 获得的偏向锁,将 Thread B 持有的锁升级为轻量级锁。 该操作需要等待全局安全点 JVM safepoint ( 此时间点, 没有线程在执行字节码) 。
    • 如果是已偏向状态, 则检测 MarkWord 中存储的 thread ID 是否等于当前 thread ID 。

      • 如果相等, 则证明本线程已经获取到偏向锁, 可以直接继续执行同步代码块
      • 如果不等, 则证明该对象目前偏向于其他线程, 需要撤销偏向锁

    从上面的偏向锁机制描述中,可以注意到

    • 偏向锁的 撤销(revoke) 是一个很特殊的操作, 为了执行撤销操作, 需要等待全局安全点(Safe Point), 此时间点所有的工作线程都停止了字节码的执行。

    偏向锁的撤销(Revoke)

    如上文提到的, 偏向锁的撤销(Revoke) 操作并不是将对象恢复到无锁可偏向的状态, 而是在偏向锁的获取过程中, 发现了竞争时, 直接将一个被偏向的对象“升级到” 被加了轻量级锁的状态。 这个操作的具体完成方式如下:

    • 在偏向锁 CAS 更新操作失败以后, 等待到达全局安全点。
      • 通过 MarkWord 中已经存在的 Thread Id 找到成功获取了偏向锁的那个线程, 然后在该线程的栈帧中补充上轻量级加锁时, 会保存的锁记录(Lock Record), 然后将被获取了偏向锁对象的 MarkWord 更新为指向这条锁记录的指针。
      • 至此, 锁撤销操作完成, 阻塞在安全点的线程可以继续执行。

    偏向锁的批量再偏向(Bulk Rebias)机制

    偏向锁这个机制很特殊, 别的锁在执行完同步代码块后, 都会有释放锁的操作, 而偏向锁并没有直观意义上的“释放锁”操作。

    那么作为开发人员, 很自然会产生的一个问题就是, 如果一个对象先偏向于某个线程, 执行完同步代码后, 另一个线程就不能直接重新获得偏向锁吗? 答案是可以, JVM 提供了批量再偏向机制(Bulk Rebias)机制

    该机制的主要工作原理如下:

    • 引入一个概念 epoch, 其本质是一个时间戳 , 代表了偏向锁的有效性
    • 从前文描述的对象头结构中可以看到, epoch 存储在可偏向对象的 MarkWord 中。
    • 除了对象中的 epoch, 对象所属的类 class 信息中, 也会保存一个 epoch 值
    • 每当遇到一个全局安全点时, 如果要对 class C 进行批量再偏向, 则首先对 class C 中保存的 epoch 进行增加操作, 得到一个新的 epoch_new
    • 然后扫描所有持有 class C 实例的线程栈, 根据线程栈的信息判断出该线程是否锁定了该对象, 仅将 epoch_new 的值赋给被锁定的对象中。
    • 退出安全点后, 当有线程需要尝试获取偏向锁时, 直接检查 class C 中存储的 epoch 值是否与目标对象中存储的 epoch 值相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。

    上述的逻辑可以在 JDK 源码中得到验证。

    sharedRuntime.cpp

    在 sharedRuntime.cpp 中, 下面代码是 synchronized 的主要逻辑

    Handle h_obj(THREAD, obj);
      if (UseBiasedLocking) {
        // Retry fast entry if bias is revoked to avoid unnecessary inflation
        ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);
      } else {
        ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);
      }
    
    • UseBiasedLocking 是 JVM 启动时, 偏斜锁是否启用的标志。
    • fast_enter 中包含了偏斜锁的相关逻辑
    • slow_enter 中绕过偏斜锁, 直接进入轻量级锁获取
    void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
                                        bool attempt_rebias, TRAPS) {
      if (UseBiasedLocking) {
        if (!SafepointSynchronize::is_at_safepoint()) {
          BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
          if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
            return;
          }
        } else {
          assert(!attempt_rebias, "can not rebias toward VM thread");
          BiasedLocking::revoke_at_safepoint(obj);
        }
        assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
      }
    
      slow_enter(obj, lock, THREAD);
    }
    
    
    • 该函数中再次保险性地做了偏斜锁是否开启的检查(UseBiasedLocking)
    • 当系统不处于安全点时, 代码通过方法 revoke_and_rebias 这个函数尝试获取偏斜锁, 如果获取成功就可以直接返回了, 如果不成功则进入轻量级锁的获取过程
    • revoke_and_rebias 这个函数名称就很有意思, 说明该函数中包含了 revoke 的操作也包含了 rebias 的操作
      • revoke 不是只应该在安全点时刻才发生吗? 答案: 有一些特殊情形, 不需要安全点也可以执行 revoke 操作
      • 此处为什么只有 rebias 操作, 没有初次的 bias 操作?答案: 首次的 bias 操作也被当成了 rebias 操作的一个特例

    revoke_and_rebias 函数的定义在 biasedLocking.cpp

    BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
      assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");
    
      // We can revoke the biases of anonymously-biased objects
      // efficiently enough that we should not cause these revocations to
      // update the heuristics because doing so may cause unwanted bulk
      // revocations (which are expensive) to occur.
      markOop mark = obj->mark();
      if (mark->is_biased_anonymously() && !attempt_rebias) {
          /* 
    		    进一步查看源码可得知, is_biased_anonymously() 为 true 的条件是对象处于可偏向状态, 
    		    且 线程ID  为空, 表示尚未偏向于任意一个线程。 
    		    此分支是进行对象的 hashCode 计算时会进入的, 根据 markWord 结构可以看到, 
    		    当一个对象处于可偏向状态时, markWord 中 hashCode 的存储空间是被占用的
    		    所以需要 revoke 可偏向状态, 以提供存储 hashCode 的空间
    		 */
        
        // We are probably trying to revoke the bias of this object due to
        // an identity hash code computation. Try to revoke the bias
        // without a safepoint. This is possible if we can successfully
        // compare-and-exchange an unbiased header into the mark word of
        // the object, meaning that no other thread has raced to acquire
        // the bias of the object.
        markOop biased_value       = mark;
        markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
        markOop res_mark = obj->cas_set_mark(unbiased_prototype, mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED;
        }
      } else if (mark->has_bias_pattern()) {
        Klass* k = obj->klass();
        markOop prototype_header = k->prototype_header();
        if (!prototype_header->has_bias_pattern()) {
          // This object has a stale bias from before the bulk revocation
          // for this data type occurred. It's pointless to update the
          // heuristics at this point so simply update the header with a
          // CAS. If we fail this race, the object's bias has been revoked
          // by another thread so we simply return and let the caller deal
          // with it.
          markOop biased_value       = mark;
          markOop res_mark = obj->cas_set_mark(prototype_header, mark);
          assert(!obj->mark()->has_bias_pattern(), "even if we raced, should still be revoked");
          return BIAS_REVOKED;
        } else if (prototype_header->bias_epoch() != mark->bias_epoch()) { 
          // The epoch of this biasing has expired indicating that the
          // object is effectively unbiased. Depending on whether we need
          // to rebias or revoke the bias of this object we can do it
          // efficiently enough with a CAS that we shouldn't update the
          // heuristics. This is normally done in the assembly code but we
          // can reach this point due to various points in the runtime
          // needing to revoke biases.
          if (attempt_rebias) {
    	    /*
    			下面的代码就是尝试通过 CAS 操作, 将本线程的 ThreadID 尝试写入对象头中
    		*/
            assert(THREAD->is_Java_thread(), "");
            markOop biased_value       = mark;
            markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
            markOop res_mark = obj->cas_set_mark(rebiased_prototype, mark);
            if (res_mark == biased_value) {
              return BIAS_REVOKED_AND_REBIASED;
            }
          } else {
            markOop biased_value       = mark;
            markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
            markOop res_mark = obj->cas_set_mark(unbiased_prototype, mark);
            if (res_mark == biased_value) {
              return BIAS_REVOKED;
            }
          }
        }
      }
    

    偏向锁 -> 轻量级锁

    从之前的描述中可以看到, 存在超过一个线程竞争某一个对象时, 会发生偏向锁的撤销操作。 有趣的是, 偏向锁撤销后, 对象可能处于两种状态。

    • 一种是不可偏向的无锁状态, 如下图(之所以不允许偏向, 是因为已经检测到了多于一个线程的竞争, 升级到了轻量级锁的机制)
      这里写图片描述

    • 另一种是不可偏向的已锁 ( 轻量级锁) 状态
      这里写图片描述

    之所以会出现上述两种状态, 是因为偏向锁不存在解锁的操作, 只有撤销操作。 触发撤销操作时:

    • 原来已经获取了偏向锁的线程可能已经执行完了同步代码块, 使得对象处于 “闲置状态”,相当于原有的偏向锁已经过期无效了。此时该对象就应该被直接转换为不可偏向的无锁状态
    • 原来已经获取了偏向锁的线程也可能尚未执行完同步代码块, 偏向锁依旧有效, 此时对象就应该被转换为被轻量级加锁的状态

    轻量级加锁过程:

    • 首先根据标志位判断出对象状态处于不可偏向的无锁状态( 如下图)
      这里写图片描述
    • 在当前线程的栈桢(Stack Frame)中创建用于存储锁记录(lock record)的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。如果在此过程中发现,
    • 然后线程尝试使用 CAS 操作将对象头中的 Mark Word 替换为指向锁记录的指针。
      • 如果成功,当前线程获得锁
      • 如果失败,表示该对象已经被加锁了, 先进行自旋操作, 再次尝试 CAS 争抢, 如果仍未争抢到, 则进一步升级锁至重量级锁。

    下图引用自博文 聊聊并发(二)Java SE1.6中的Synchronized展示了两个线程竞争锁, 最终导致锁膨胀为重量级锁的过程。

    **注意: 下图中第一个标绿 MarkWord 的起始状态是HashCode|age|0|01 是偏向锁未被启用时, 分配对象后的状态, 所以在图中并没有偏向锁这一流程的体现, 是直接从无锁状态进入了轻量级锁的状态

    这里写图片描述

    重量级锁

    重量级锁依赖于操作系统的互斥量(mutex) 实现, 其具体的详细机制此处暂不展开, 日后可能补充。 此处暂时只需要了解该操作会导致进程从用户态与内核态之间的切换, 是一个开销较大的操作。

    存疑的问题

    1. 在锁膨胀的图例中, 线程 2 在线程 1 尚未释放锁时, 即将对象头修改为指向重量级锁的状态, 这个操作具体如何完成, 是否需要等待全局安全点?笔者尚未细究

    2. 轻量级锁的第一次获取时, 如果 CAS 操作失败, 按照 聊聊并发(二)Java SE1.6中的Synchronized 的描述, 会进行自旋的尝试。 但按照 Synchronization and Object Locking 的描述, 会去检测已加的锁是归属于自身线程, 没有提到自旋操作。 具体哪一种是正确的行为, 有待研究源码。

    3. biasedLocking.cpp中的方法 revoke_and_rebias 存在 4 个条件分支, 其中笔者添加了注释的两个分支其主要逻辑已经清晰, 但未添加注释的两个分支具体逻辑笔者尚不清楚, 有待进一步研究

    展开全文
  • 锁升级 无锁——》偏向锁——》自旋锁——》重量级锁 用户态到内核态的切换消耗资源 1、刚开始无锁; 2、来一个线程的时候锁升级为偏向锁(记录线程号--当前线程指针); 3、再来一个线程竞争,撤销偏向锁;锁升级为...

    锁升级过程         无锁——》偏向锁——》自旋锁——》重量级锁                  


    1、刚开始无锁;
    2、来一个线程的时候锁升级为偏向锁(记录线程号--当前线程指针);
    3、再来一个线程竞争,撤销偏向锁;锁升级为轻量级锁(自旋锁、无锁)---》指向线程栈中 Lock Record 的指针,通过CAS方式竞争锁 谁先把自己的 Lock Record指针贴上去,谁先获取锁;另外一个线程一直自旋等待
    4、有线程超过10次自旋或自旋线程数超过CPU核数的一半,自动升级为重量级锁;jdk1.6之后 jvm自己控制升级为重量级锁。
    5、无锁——》偏向锁——》轻量级锁 升级都是   用户态;随着自旋锁一直等待,会消耗cpu,所以会继续升级为重量级锁(获取到重量级锁的线程会进入等待队列,避免消cpu),用户态到内核态的过程

     

     

    展开全文
  • 一次面试中有被问到Java中偏向锁、自旋锁等,特地查了下资料,发现这都是Java虚拟机的锁优化,查漏补缺,写篇博客记录下。 1.偏向锁 概述:是针对加锁操作的优化,在一次线程获得锁时,那么该锁进入偏向模式,...
  • 【笔记】java中的所有锁 锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现...
  • Java 偏向锁

    2018-07-15 17:43:14
    Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程,该...
  • java 偏向锁

    千次阅读 2016-05-08 19:05:57
    java 偏向锁  Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的...
  • 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态。 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤5,否则进入步骤3。 如果线程ID并未指向当前线程,则通过...
  • Java偏向锁

    千次阅读 2015-06-05 14:07:45
    概述偏向锁是JDK 1.6提出的一种锁优化方式,起核心思想是如果程序没有竞争,则取消之前已经取得锁的线程的同步操作。也就是说,某一个锁被一...在JVM可以使用-XX:+UseBiasedLocking设置启用偏向锁。实验看如下的代码:
  • Java中偏向锁是什么

    2020-10-30 10:06:11
    关于偏向锁的知识,网上一大片文章,各种理论讲的一个比一个好。但是你知道如何去验证偏向锁呢?有相关的源码或者代码给你看过吗? 那么这篇文章只是为了证明偏向锁是否真的存在,它为什么要比重量级锁快? 在有了上...
  • java锁-偏向锁

    2019-12-12 15:33:50
    偏向锁: HotSpot作者发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获取锁的代价更低,引入了偏向锁,当一个线程访问同步块并获取锁时,会在对象头和栈贞的锁记录里存储锁偏向的...
  • java的同步锁知识点 编码如果使用锁可以使用synchronized关键字 Synchronized同步锁jvm内置的隐式锁(相对... jdk1.6对于Synchronzied同步锁提出了偏向锁、轻量级锁、重量级锁的概念(synchronized的性能优化) .
  • 为了减少获得锁和释放锁带来的性能消耗,java引入了偏向锁和轻量级锁,并对锁的存储结构进行了升级。 synchronized在JVM的实现原理 JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,monitorenter...
  • Java中的锁的状态有四种:无锁、偏向锁、轻量级锁、重量级锁,状态会随竞争状态改变,低等级的锁可以升级为高等级的锁,即锁升级;但反之不行,没有锁降级。 要理解锁升级,首先要了解一些基本概念: 加锁行为到底锁...
  • 之前的synchronized都是重量级锁,1.6改良了 ,先是偏向锁到轻量级锁,到最后的重量级锁。 偏向锁 不过,当线程执行到临界区(critical section)时,此时会利用**CAS(Compare and Swap)**操作,将线程ID插入到Mark...
  • Java中锁是实现线程同步的一个比不可少的组件。 Java锁按照等级分可以为 偏向锁->轻量级锁->重量级锁。 他们对应的等级逐级提升。 1.前序知识 首先我们要知道对象头(Mark Word),简单的来说对象头是保存...
  • 参考:java 的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁 Java并发——Synchronized优化(轻量级锁、偏向锁) 本文将简单介绍HotSpot虚拟机中用到的锁优化技术(这里的锁指的都是synchronized)。 自旋锁 互斥...
  • Java 偏向锁的升级

    2020-09-11 11:14:46
    示例:线程1当前拥有偏向锁对象,线程2是需要竞争到偏向锁。 线程2来竞争锁对象; 判断当前对象头是否是偏向锁; 判断拥有偏向锁的线程1是否还存在; 线程1不存在,直接设置偏向锁标识为0(线程1执行完毕后,不会主动...
  • 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态。 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤5,否则进入步骤3。 如果线程ID并未指向当前线程,则通过...
  • 锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)
  • CAS操作 3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。 执行:当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一...对象(方法) 针...
  • Java的对象头的MarkWord 32位操作系统: 64位操作系统: Monitor 当使用synchronized获得时: synchronized(obj){//重量级 //临界区代码 } obj对象的MarkWord的指针(ptr_to_heavyweight_monitor)...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,464
精华内容 585
关键字:

java中偏向锁

java 订阅