精华内容
下载资源
问答
  • 在本文中小编给的大家整理了关于Java锁的升级策略 偏向 轻量级锁 重量级锁的相关知识点内容,需要的朋友们参考下。
  • 很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,本文详细介绍了Java SE1.6中为了减少获得锁和释放带来的性能消耗而引入的偏向锁和轻量级锁,...
  • 的状态总共有四种:无锁状态、偏向轻量级锁和重量级锁。随着的竞争,可以从偏向升级到轻量级锁,再升级的重量级锁(但是的升级是单向的,也就是说只能从低到高升级,不会出现的降级)

    写在前面

    今天我们来聊聊 Synchronized 里面的各种锁:偏向锁、轻量级锁、重量级锁,以及三个锁之间是如何进行锁膨胀的。先来一张图来总结
    在这里插入图片描述

    提前了解知识

    锁的升级过程

    锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)

    Java 对象头

    因为在Java中任意对象都可以用作锁,因此必定要有一个映射关系,存储该对象以及其对应的锁信息(比如当前哪个线程持有锁,哪些线程在等待)。一种很直观的方法是,用一个全局map,来存储这个映射关系,但这样会有一些问题:需要对map做线程安全保障,不同的synchronized之间会相互影响,性能差;另外当同步对象较多时,该map可能会占用比较多的内存。所以最好的办法是将这个映射关系存储在对象头中,因为对象头本身也有一些hashcode、GC相关的数据,所以如果能将锁信息与这些信息共存在对象头中就好了。

    在JVM中,对象在内存中除了本身的数据外还会有个对象头,对于普通对象而言,其对象头中有两类信息:mark word和类型指针。另外对于数组而言还会有一份记录数组长度的数据。类型指针是指向该对象所属类对象的指针,mark word用于存储对象的HashCode、GC分代年龄、锁状态等信息。在32位系统上mark word长度为32bit,64位系统上长度为64bit。为了能在有限的空间里存储下更多的数据,其存储格式是不固定的,在32位系统上各状态的格式如下:
    在这里插入图片描述
    可以看到锁信息也是存在于对象的mark word中的。当对象状态为偏向锁(biasable)时,mark word存储的是偏向的线程ID;当状态为轻量级锁(lightweight locked)时,mark word存储的是指向线程栈中Lock Record的指针;当状态为重量级锁(inflated)时,为指向堆中的monitor对象的指针。

    全局安全点(safepoint)

    safepoint这个词我们在GC中经常会提到,简单来说就是其代表了一个状态,在该状态下所有线程都是暂停的。

    偏向锁

    一个线程反复的去获取/释放一个锁,如果这个锁是轻量级锁或者重量级锁,不断的加解锁显然是没有必要的,造成了资源的浪费。于是引入了偏向锁,偏向锁在获取资源的时候会在资源对象上记录该对象是偏向该线程的,偏向锁并不会主动释放,这样每次偏向锁进入的时候都会判断该资源是否是偏向自己的,如果是偏向自己的则不需要进行额外的操作,直接可以进入同步操作。

    偏向锁获取过程

    • 访问Mark Word中偏向锁标志位是否设置成1,锁标志位是否为01——确认为可偏向状态。
    • 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。
    • 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。
    • 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。
    • 执行同步代码。

    偏向锁的释放

    偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点safepoint,它会首先暂停拥有偏向锁的线程A,然后判断这个线程A,此时有两种情况:
    在这里插入图片描述

    批量重偏向

    为什么有批量重偏向

    当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获得锁时,就需要等到safe point时将偏向锁撤销为无锁状态或升级为轻量级/重量级锁。这个过程是要消耗一定的成本的,所以如果说运行时的场景本身存在多线程竞争的,那偏向锁的存在不仅不能提高性能,而且会导致性能下降。因此,JVM中增加了一种批量重偏向/撤销的机制。

    批量重偏向的原理

    • 首先引入一个概念epoch,其本质是一个时间戳,代表了偏向锁的有效性,epoch存储在可偏向对象的MarkWord中。除了对象中的epoch,对象所属的类class信息中,也会保存一个epoch值。

    • 每当遇到一个全局安全点时(这里的意思是说批量重偏向没有完全替代了全局安全点,全局安全点是一直存在的),比如要对class C 进行批量再偏向,则首先对 class C中保存的epoch进行增加操作,得到一个新的epoch_new

    • 然后扫描所有持有 class C 实例的线程栈,根据线程栈的信息判断出该线程是否锁定了该对象,仅将epoch_new的值赋给被锁定的对象中,也就是现在偏向锁还在被使用的对象才会被赋值epoch_new。

    • 退出安全点后,当有线程需要尝试获取偏向锁时,直接检查 class C 中存储的 epoch 值是否与目标对象中存储的 epoch 值相等, 如果不相等,则说明该对象的偏向锁已经无效了(因为(3)步骤里面已经说了只有偏向锁还在被使用的对象才会有epoch_new,这里不相等的原因是class C里面的epoch值是epoch_new,而当前对象的epoch里面的值还是epoch),此时竞争线程可以尝试对此对象重新进行偏向操作。

    轻量级锁

    轻量级锁的获取过程

    • 在代码进入同步块的时候,如果同步对象锁状态为偏向状态(就是锁标志位为“01”状态,是否为偏向锁标志位为“1”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。官方称之为 Displaced Mark Word(所以这里我们认为Lock Record和 Displaced Mark Word其实是同一个概念)。这时候线程堆栈与对象头的状态如图所示:
      在这里插入图片描述

    • 拷贝对象头中的Mark Word复制到锁记录中。

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

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

    • 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,现在是重入状态,那么设置Lock Record第一部分(Displaced Mark Word)为null,起到了一个重入计数器的作用。下图为重入三次时的lock record示意图,左边为锁对象,右边为当前线程的栈帧,重入之后然后结束。接着就可以直接进入同步块继续执行。
      在这里插入图片描述
      如果不是说明这个锁对象已经被其他线程抢占了,说明此时有多个线程竞争锁,那么它就会自旋等待锁,一定次数后仍未获得锁对象,说明发生了竞争,需要膨胀为重量级锁。

    轻量级锁的解锁过程

    • 通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。

    • 如果替换成功,整个同步过程就完成了。

    • 如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

    重量级锁

    重量级锁加锁和释放锁机制

    • 调用omAlloc分配一个ObjectMonitor对象,把锁对象头的mark word锁标志位变成 “10 ”,然后在mark word存储指向ObjectMonitor对象的指针
    • ObjectMonitor中有两个队列,_WaitSet_EntryList,用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用wait()方法,将释放当前持有的monitorowner变量恢复为null,count自减1,同时该线程进入WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示
      在这里插入图片描述

    Synchronized同步代码块的底层原理

    同步代码块的加锁、解锁是通过 Javac 编译器实现的,底层是借助monitorentermonitorerexit,为了能够保证无论代码块正常执行结束 or 抛出异常结束,都能正确释放锁,Javac 编译器在编译的时候,会对monitorerexit进行特殊处理,举例说明:

    public class Hello {
        public void test() {
            synchronized (this) {
                System.out.println("test");
            }
        }
    }
    

    通过 javap -c 查看其编译后的字节码:

    public class Hello {
      public Hello();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
      public void test();
        Code:
           0: aload_0
           1: dup
           2: astore_1
           3: monitorenter
           4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           7: ldc           #3                  // String test
           9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          12: aload_1
          13: monitorexit
          14: goto          22
          17: astore_2
          18: aload_1
          19: monitorexit
          20: aload_2
          21: athrow
          22: return
        Exception table:
           from    to  target type
               4    14    17   any
              17    20    17   any
    }
    

    从字节码中可知同步语句块的实现使用的是monitorentermonitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取mark word里面存储的monitor,当 monitor的进入计数器为 0,那线程可以成功取得monitor,并将计数器值设置为1,取锁成功。

    如果当前线程已经拥有 monitor 的持有权,那它可以重入这个 monitor ,重入时计数器的值也会加 1。倘若其他线程已经拥有monitor的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor并设置计数器值为0 ,其他线程将有机会持有 monitor

    值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorentermonitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从上面的字节码中也可以看出有两个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。

    同步方法底层原理

    同步方法的加锁、解锁是通过 Javac 编译器实现的,底层是借助ACC_SYNCHRONIZED访问标识符来实现的,代码如下所示:

    public class Hello {
        public synchronized void test() {
            System.out.println("test");
        }
    }
    

    方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有monitor,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。

    下面我们看看字节码层面如何实现:

    public class Hello {
      public Hello();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
      public synchronized void test();
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #3                  // String test
           5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
    

    锁的其他优化

    • 适应性自旋(Adaptive Spinning):从轻量级锁获取的流程中我们知道,当线程在获取轻量级锁的过程中执行CAS操作失败时,是要通过自旋来获取重量级锁的。问题在于,自旋是需要消耗CPU的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费CPU资源。解决这个问题最简单的办法就是指定自旋的次数,例如让其循环10次,如果还没获取到锁就进入阻塞状态。但是JDK采用了更聪明的方式——适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。
    • 锁粗化(Lock Coarsening):锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。举个例子:
    public  void lockCoarsening() {
        int i=0;
        synchronized (this){
            i=i+1;
        }
        synchronized (this){
            i=i+2;
        }
    }
    

    上面的两个同步代码块可以变成一个

    public  void lockCoarsening() {
        int i=0;
        synchronized (this){
            i=i+1;
            i=i+2;
        }
    }
    
    • 锁消除(Lock Elimination):锁消除即删除不必要的加锁操作的代码。比如下面的代码,下面的for循环完全可以移出来,这样可以减少加锁代码的执行过程
    public  void lockElimination() {
        int i=0;
        synchronized (this){
            for(int c=0; c<1000; c++){
                System.out.println(c);
            }
            i=i+1;
        }
    }
    
    展开全文
  • 其实在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. 第一个线程未执行完毕,此时第二个线程获取锁失败,那么会进行自旋,当自旋达到一定次数后,就会升级成轻量级锁

    轻量级锁流程见下图:

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

     

     

    展开全文
  • 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-&gt;mark()-&gt;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 个条件分支, 其中笔者添加了注释的两个分支其主要逻辑已经清晰, 但未添加注释的两个分支具体逻辑笔者尚不清楚, 有待进一步研究

    展开全文
  • 轻量级锁是JDK 1.6之中加入的新型机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统而言的,因此传统的机制就称为“重量级。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,...

    轻量级锁是JDK 1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

    要理解轻量级锁,以及后面会讲到的偏向锁的原理和运作过程,必须从HotSpot虚拟机的对象(对象头部分)的内存布局开始介绍。HotSpot虚拟机的对象头(Object Header)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,官方称它为“Mark Word”,它是实现轻量级锁和偏向锁的关键。另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。


    对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如,在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32bit空间中的25bit用于存储对象哈希码(HashCode),4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容见表。

    存储内容标志位状态
    对象哈希码,对象分代年龄01未锁定
    指向锁记录的指针00轻量级锁定
    指向重量级锁的指针10膨胀(重量级锁定)
    空,不需要记录信息11GC标记
    偏向线程ID,偏向时间戳,对象分代年龄01可偏向

    在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的MarkWord的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word),这时候线程堆栈与对象头的状态如图13-3所示。


    然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后2bit)将转变为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图13-4所示。

    如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果只说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

    上面描述的是轻量级锁的加锁过程,它的解锁过程也是通过CAS操作来进行的,如果对象的Mark Word仍然指向着线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

    轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

    2偏向锁

    偏向锁也是JDK 1.6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。
               偏向锁的“偏”,就是偏心的“偏”、偏袒的“偏”,它的意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。假设当前虚拟机启用了偏向锁(启用参数-XX:+UseBiasedLocking,这是JDK 1.6的默认值),那么,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。
                当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级锁那样执行。偏向锁、轻量级锁的状态转化及对象Mark Word的关系如图13-5所示。

    锁的优缺点对比

     

    展开全文
  • 参考博客:... 参考博客https://blog.csdn.net/hl_java/article/details/70148453 参考博客:https://blog.csdn.net/zqz_zqz/article/details/70233767 参考博客:http://www.sohu.com...
  • synchronized原理,偏向轻量级锁重量级锁升级
  • 是偏向于一个给定的线程,锁定解锁可以在不使用原子操作的情况下由该线程执行。当的偏差被撤销时,它将恢复到正常状态下面描述的锁定方案。注意:Hotspot不会在JVM启动的最初几秒(目前是4秒)内启用偏向锁定...
  • HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Object Header)、实例数据(Instance Data)对齐填充(Padding)。 对象头(Object Header)  JVM的对象头包括二/三部分信息:1、Mark ...
  • 在学习sychronized关键字及其实现细节的时候,发现java中的三种,偏向轻量级锁重量级锁其实也有很多值得探究的地方,引入偏向是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级...
  • 上篇文章已经分析了Java对象头构成、源码及其对象头的调试,本篇将分析偏向轻量级锁重量级锁的实现及其演变过程。由于涉及到c++源码,估计不少同学没兴趣看,因此重点多以图+源码辅助分析。 通过本篇文章,你...
  • 前面提到了java的4种,他们分别是重量级锁、自旋轻量级锁和偏向, 不同的有不同特点,每种只有在其特定的场景下,才会有出色的表现,java中没有哪种能够在所有情况下都能有出色的效率,引入这么多...
  • 重量级锁(Mutex Lock) Synchronized 是通过对象内部的一个叫做监视器(monitor)来实现的。但是监视器本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用户态...
  • java中每个对象都可作为有四种级别,按照量级从轻到重分为:无锁、偏向轻量级锁重量级锁。并且只能升级不能降级。 在讲这三个之前,我先给大家讲清楚自旋对象头的概念。 自旋 现在假设有这么一...
  • JDK1.6为了减少获得锁和释放所带来的性能消耗,引入了“偏向轻量级锁”,所以在JDK1.6里一共有四种状态,无锁状态,偏向状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。可以升级但不...
  • 1,偏向。 偏向:顾名思义,偏心与某一个线程,而他偏心的线程就是第一个访问该...去检验该线程是不是他所偏向的(其实就是检测Mark Word中保存的线程id是否当前线程所匹配),那么该线程访问临界资源的...
  • 很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,Java SE1.6中为了减少获得锁和释放带来的性能消耗而引入的偏向锁和轻量级锁,以及的存储结构...
  • Synchronized一直是多线程并发编程中的重要角色,但是在Java1.6中,为了减少获得带来的性能消耗,引入了偏向锁和轻量级锁。 目录 的状态: 偏向 轻量级锁 重量级锁 偏向轻量级锁重量级锁应用场景 ...
  • 轻量级锁、偏向重量级锁详情

    千次阅读 热门讨论 2018-11-17 13:48:47
    这篇文章是上篇文章是否真的理解了偏向轻量级锁重量级锁膨胀)、自旋消除、粗化,知道重偏向吗?的补充,对于偏向,网上有些对于它的原理解读过于简单,简单得似乎是错误的,最显眼的是对于Mark...
  • JAVA轻量级锁简介

    千次阅读 2018-10-20 17:39:47
    Java Object头文件中,有一个“Mark Word”对象,它是实现轻量级锁的关键。 Mark Word的数据结构非固定,根据不同的状态会有不同的结构。 比如在32位的HotSpot虚拟机中对象未被锁定的状态...
  • Synchronized升级的过程: 一个对象A刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程T1来访问它的时候,它会偏向T1,此时,对象A...
  • 偏向轻量级锁重量级锁的区别与膨胀

    万次阅读 多人点赞 2017-03-19 20:44:01
    一直被这三个的膨胀问题所困扰,不知道到底实在什么时候会有偏向升级到轻量级锁,什么时候由轻量级锁升级到重量级锁。找到好久,也没有找到简洁明了的答案。  综合多个方面的描述综合自己的理解,特地记录下来...
  • synchronized中重量级锁、偏向锁和轻量级锁的区别

    千次阅读 多人点赞 2019-08-06 20:29:36
    我们先介绍重量级锁,然后介绍优化后的轻量级锁和偏向。 0.对象头以及Mark Word 1重量级锁 重量级的synchronized有三个用法: 普通同步方法,的是当前实例对象。 静态同步方法,的是当前类的clas...
  • 轻量级锁重量级锁都是啥玩意

    千次阅读 多人点赞 2019-07-03 08:35:40
    在JDK1.6以后,为了减少消耗,进行了很多的升级。并且有了四种状态,从低到高 - 无锁状态 - 偏向状态 - 轻量级锁状态 - 重量级锁状态 下面就介绍一下这四种不同等级的
  • 三、的优化 1、升级 2、粗化 3、消除 一、Synchronized使用场景 Synchronized是一个同步关键字,在某些多线程场景下,如果不进行同步会导致数据不安全,而Synchronized关键字就是用于代码同步。什么...
  • 轻量级重量级是一个相对的概念,主要是对应用框架使用方便性所提供服务特性等方面做比较的。 比方说EJB就是一个重量级的框架,因为它对所编写的代码有限制,同时它也提供分布式等复杂的功能。相比之下,Spring...
  • 1、 Java 对象头 对象头 对象头包含两部分:运行时元数据(Mark Word)类型指针 (Klass Word) 运行时元数据 哈希值(HashCode),可以看作是堆中对象的地址 GC分代年龄(年龄计数器) 状态标志 线程持有的...
  • 轻量级锁状态 重量级锁状态 四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。 要注意的是,这四种状态都不是Java语言中的,而是Jvm为了提高的获取与释放效率而做的优化(使用...
  • 从synchronized举例,在java1.6当中为了减少获得锁和释放带来的性能消耗而引入的偏向锁和轻量级锁,以及的存储结构升级过程。 ·对于普通同步方法,是当前实例对象。 ·对于静态同步方法,是当前类的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 42,816
精华内容 17,126
关键字:

java轻量级和重量级锁

java 订阅