精华内容
下载资源
问答
  • 原理:偏向、重量

    万次阅读 2017-12-18 16:15:16
    java中每个对象都可作为有四种级别,按照量级从轻到重分为:无锁、偏向量级、重量级。每个对象一开始都是无锁的,随着线程间争夺,越激烈,的级别越高,并且只能升级不能降级。

     java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。

    一、java对象头

     锁的实现机制与java对象头息息相关,锁的所有信息,都记录在java的对象头中。用2字(32位JVM中1字=32bit=4baye)存储对象头,如果是数组类型使用3字存储(还需存储数组长度)。对象头中记录了hash值、GC年龄、锁的状态、线程拥有者、类元数据的指针。

    Java对象头的结构
    在不同锁状态下Mark Word的结构(32位下)

    二、偏向锁

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

     那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步,退出同步也,无需每次加锁解锁都去CAS更新对象头,如果不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候需要锁膨胀为轻量级锁,才能保证线程间公平竞争锁。

    1.加锁


    偏向锁加锁发生在偏向线程第一次进入同步块时,CAS原子操作尝试更新对象的Mark Word(偏向锁标志位为”1”,记录偏向线程的ID)。

    2.撤销偏向锁

     当有另一个线程来竞争锁的时候,就不能再使用偏向锁了,要膨胀为轻量级锁。
    竞争线程尝试CAS更新对象头失败,会等待到全局安全点(此时不会执行任何代码)撤销偏向锁。
    撤销偏向锁的过程
    撤销偏向锁的过程

    三、轻量级锁

     轻量锁与偏向锁不同的是:
    1. 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
    2. 每次进入退出同步块都需要CAS更新对象头
    3. 争夺轻量级锁失败时,自旋尝试抢占锁

     可以看到轻量锁适合在竞争情况下使用,其自旋锁可以保证响应速度快,但自旋操作会占用CPU,所以一些计算时间长的操作不适合使用轻量级锁。

    1.加锁

     加锁过程和偏向锁加锁差不多,也是CAS修改对象头,只是修改的内容不同。
    1. 在MarkWord中保存当前线程的指针
    2. 修改锁标识位为“00”

    采用CAS操作的原因是,不想在加锁解锁上再加同步

     如果对象处于无锁状态(偏向锁标志位为”0”,锁标志位为”01”),会在线程的栈中开辟个锁记录空间(Lock Record),将Mark Word拷贝一份到Lock Record中,称为Displaced Mark Word,在Lock Record中保存对象头的指针(owner)。
    接下来CAS更新MarkWord,将MarkWord指向当前线程,owner指向MarkWord,如果失败了,则意味着出现了另一个线程竞争锁,此时需要锁膨胀为轻量级锁。

    CAS操作前的栈和MarkWord状态
    CAS操作后的栈和MarkWord状态

    2.解锁

     用CAS操作锁置为无锁状态(偏向锁位为”0”,锁标识位为”01”),若CAS操作失败则是出现了竞争,锁已膨胀为重量级锁了,此时需要释放锁(持有重量级锁线程的指针位为”0”,锁标识位为”10”)并唤醒重量锁的线程。

    3.膨胀为重量级锁

     当竞争线程尝试占用轻量级锁失败多次之后,轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。
    膨胀为重量级锁

    三、重量级锁

     重量级锁的加锁、解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下。

    四、参考

    1. 《Java并发编程的艺术》
    2. 《轻量级锁与偏向锁》
    3. 《Synchronized下的三种锁:偏向锁 轻量锁 重量锁 理解》
    4. 《JAVA锁的膨胀过程和优化》
    展开全文
  • 关于并发编程下的各种机制的简单介绍和总结



    (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/74139773冷血之心的博客)


    以下内容摘抄整理于网络和书籍:


    1、自旋锁:

    采用让当前线程不停的在循环体内执行实现,当循环的条件被其它线程改变时才能进入临界区

    举例如下:



    优缺点分析:

    由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。




    2、阻塞锁:

    阻塞锁改变了线程的运行状态,让线程进入阻塞状态进行等待,当获得相应的信号(唤醒或者时间)时,才可以进入线程的准备就绪状态,转为就绪状态的所有线程,通过竞争,进入运行状态。


    优缺点分析:

    阻塞锁的优势在于,阻塞的线程不会占用cpu时间,不会导致 CPu占用率过高,但进入时间以及恢复时间都要比自旋锁略慢。在竞争激烈的情况下 阻塞锁的性能要明显高于自旋锁。



    3、重入锁:

    Java中的synchronized同步块是可重入的。这意味着如果一个java线程进入了代码中的synchronized同步块,因此获得了该同步块使用的同步对象对应的管程上的锁,那么这个线程可以进入由同一个管程对象所同步的另

    一个java代码块。


    ReentrantLock与synchronized比较:

    1. 前者使用灵活,但是必须手动开启和释放锁
    2. 前者扩展性好,有时间锁等候(tryLock( )),可中断锁等候(lockInterruptibly( )),锁投票等,适合用于高度竞争锁和多个条件变量的地方
    3. 前者提供了可轮询的锁请求,可以尝试去获取锁(tryLock( )),如果失败,则会释放已经获得的锁。有完善的错误恢复机制,可以避免死锁的发生。
    优缺点分析:

    可重入锁的最大优点就是可以避免死锁。缺点是必须手动开启和释放锁。


    偏向锁、轻量锁和重量锁的优缺点总结如下:




    如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~



    本群给大家提供一个学习交流的平台,内设菜鸟Java管理员一枚、精通算法的金牌讲师一枚、Android管理员一枚、蓝牙BlueTooth管理员一枚、Web前端管理一枚以及C#管理一枚。欢迎大家进来交流技术。





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

    写在前面

    今天我们来聊聊 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;
        }
    }
    
    展开全文
  • 量级与偏向

    万次阅读 2015-04-18 20:30:42
    量级与偏向 要了解量级与偏向的原理和运作过程,需要先了解Hotspot虚拟机的对象头部分的内存布局。 1. 对象头 对象自身的运行时数据 如:哈希吗(HashCode)、GC分代年龄(Generational GC Age)等...

    轻量级锁与偏向锁

    要了解轻量级锁与偏向锁的原理和运作过程,需要先了解Hotspot虚拟机的对象头部分的内存布局。

    1. 对象头

    • 对象自身的运行时数据
      如:哈希吗(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,简称“Mark Word”
    • 如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2Word存储对象头。
    • 指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。
    • 对象头信息是与对象自身定义的数据无关的额外存储成本。它会根据对象的状态复用自己的存储空间。例如:在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32bit空间中的25bit用于存储对象哈希吗(HashCode),4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0

    其他状态下对象的存储内容

    锁状态包括:轻量级锁定、重量级锁定、GC标记、可偏向

    32位JVM的Mark Word的默认存储结构如下:

    64位JVM下, Mark Word是64bit大小的,存储结构如下:


    2. 偏向锁

    JDK1.6引入

    • 优点:消除数据在无竞争情况下的同步原语,提高性能。
    • 偏向锁与轻量级锁理念上的区别:
      • 轻量级锁:在无竞争的情况下使用CAS操作去消除同步使用的互斥量
      • 偏向锁:在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了
    • 意义:锁偏向于第一个获得它的线程。如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。
    • 相关参数:
      • 默认-XX:+UseBiasedLocking=true
      • -XX:-UseBiasedLocking=false关闭偏向锁
      • 应用程序启动几秒钟之后才激活
      • -XX:BiasedLockingStartupDelay = 0关闭延迟

    2.1 加锁

    • 当锁对象第一次被线程获取的时候,虚拟机把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中的偏向线程ID,并将是否偏向锁的状态位置置为1。

      • 如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,直接检查ThreadId是否和自身线程Id一致,
        • 如果一致,则认为当前线程已经获取了锁,虚拟机就可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。
    • 当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级那样执行。

    偏向锁、轻量级锁的状态转化及对象Mark Word的关系如下图:

    2.2偏向锁的获取和撤销流程

    挂起拥有偏向锁的线程后,存在竞争则将Mark Word恢复为轻量级锁,并将该锁赋予当前堆栈中最近的一个lock record。

    2.3 总结

    偏向锁可以提高带有同步但无竞争的程序性能。如果程序中大多数的锁总是被多个不同的线程访问,那偏向模式就是多余的。在具体情形分析下,禁止偏向锁优反而可能提升性能。

    3. 轻量级锁

    3.1 加锁

    • 在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁记录目前的Mark Word的拷贝(称为Displaced Mark Word)
      • 拷贝mark word的作用:为了不想在lock与unlock这种底层操作上再加同步。
      • 修改Object mark word轻量级锁指针作用:告诉其他线程,该object monitor已被占用
      • owner指向object mark word作用:在下面的运行过程中,识别哪个对象被锁住了。

    这时候堆栈与对象头的状态如下

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

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

    3.2 解锁

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

    3.3 lock,unlock与mark word之间的联系

    JDK1.5以后,不存在OS原语同步了。

    3.4 两个线程同时争夺锁,导致锁膨胀的流程图

    3.5 总结

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


    参考

    1. 深入理解Java虚拟机:JVM高级特性与最佳实践
    2. http://blog.csdn.net/songylwq/article/details/5585734
    3. http://ifeve.com/java-synchronized/
    4. http://blog.163.com/silver9886@126/blog/static/35971862201472274958280/
    展开全文
  • Java线程并发中常见的--自旋 偏向 量级 量级
  • 偏向量级,重量级

    千次阅读 2016-07-06 20:31:03
    偏向量级,重量级标签(空格分隔): java 并发 偏向偏向,顾名思义,它会偏向于第一个访问的线程,如果在接下来的运行过程中,该没有被其他的线程访问,持有偏向的线程将永远不需要触发...
  • 三、的优化 1、升级 2、粗化 3、消除 一、Synchronized使用场景 Synchronized是一个同步关键字,在某些多线程场景下,如果不进行同步会导致数据不安全,而Synchronized关键字就是用于代码同步。什么...
  • 量级、偏向、重量级详情

    千次阅读 2018-11-17 13:48:47
    这篇文章是上篇文章是否真的理解了偏向量级、重量级膨胀)、自旋消除、粗化,知道重偏向吗?的补充,对于偏向,网上有些对于它的原理解读过于简单,简单得似乎是错误的,最显眼的是对于Mark...
  • 自旋可以使线程在没有取得的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得继续执行。若线程依然不能获得,才会被挂起。 使用...
  • 一共有4种状态,级别从低到高依次是:无锁状态、偏向状态、量级状态和重量级状态; Java对象头Mark Word 这里重点关注对象头中的Mark Word。 MarkWord里默认数据是存储对象的HashCode等信息,但是在运行
  • 宏观上分为:悲观、乐观 悲观:认为写多读少,每次都会上。 乐观:读多写少。 自旋:如果持有的线程能在很短的时间内释放资源,那么那些等待竞争的线程就不需要做内核态与用户态之间的切换进入...
  • 对象共有四种状态:无锁、偏向量级、重量级竞争程度依次加重。 对象可以升级但不能降级,意味着偏向升级成量级后不能降级成偏向。 自旋是一种获取的策略,存在于获取量级的过程中...
  • 如果在运行过程中,遇到了其他线程抢占持有偏向的线程会被挂起,JVM会消除它身上的偏向,将恢复到标准的量级。 它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。 ...
  • jdk1.6之后,因为线程状态间的切换低效,所以对synchronized实现同步功能进行了优化,引入偏向量级,自旋和重量级等概念,来提高synchronized实现同步的效率。 synchronized获取不到之后,不会立即...
  • 偏向

    千次阅读 2015-07-19 18:36:16
    偏向  http://blog.163.com/silver9886@126/blog/static/35971862201472274958280/ Java偏向(Biased Locking)是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行...
  • 偏向量级

    千次阅读 2017-01-05 17:57:50
    偏向 为了让线程获取的代价减少,从而引入了偏向。当一个线程访问同步块并获取的时候,会在对象头和栈帧中的琐记录里存储偏向的线程ID,以后线程在进入同一个同步块时,不需要进行CAS操作,来加锁和解锁...
  • 很多人都会称呼它为重量级,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,Java SE1.6中为了减少获得和释放带来的性能消耗而引入的偏向量级,以及的存储结构和...
  • java中每个对象都可作为有四种级别,按照量级从轻到重分为:无锁、偏向量级、重量级。并且只能升级不能降级。 在讲这三个之前,我先给大家讲清楚自旋和对象头的概念。 自旋 现在假设有这么一...
  • JDK1.6为了减少获得和释放所带来的性能消耗,引入了“偏向”和“量级”,所以在JDK1.6里一共有四种状态,无锁状态,偏向状态,量级状态和重量级状态,它会随着竞争情况逐渐升级。可以升级但不...
  • 偏向量级与重量级的区别与膨胀

    万次阅读 多人点赞 2017-03-19 20:44:01
    一直被这三个的膨胀问题所困扰,不知道到底实在什么时候会有偏向升级到量级,什么时候由量级升级到重量级。找到好久,也没有找到简洁明了的答案。  综合多个方面的描述综合自己的理解,特地记录下来...
  • 每次去拿数据的时候都认为别人不会修改,所以不会上,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取的方式是在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样更新)...
  • 一、简介 在讲解这些概念之前,我们要明确的是这些不等同于Java API中的ReentratLock这种,这些是概念上的,是JDK1.6中为了对synchronized同步关键字进行优化而产生的的机制。这些的启动和关闭策略可以...
  • 量级

    千次阅读 2019-04-27 16:05:32
    量级不是为了代替重量级,它的本意是在没有多线程竞争的前提下,减少传统的重量级使用操作系统互斥量产生的性能消耗。 加锁 首先,JVM在当前线程栈帧中创建用于存储记录的空间; 将对象头中的Mark Word...
  • 量级状态 重量级状态 四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。 要注意的是,这四种状态都不是Java语言中的,而是Jvm为了提高的获取与释放效率而做的优化(使用...
  • 如果持有的线程能在很短时间内释放资源,那么那些等待竞争的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,只需让线程执行一个忙循环(自旋),等持有的线程释放后即可立即获取,这样就避免...
  • 浅析量级

    千次阅读 2018-03-19 15:51:30
    量级 来看机制。(目前 上的唯一一张图。= =。 因为有些东西没有图的话 是很难理清楚的 - - )对象是否被某个线程的锁定的依据是, 对象头中记录的信息。 mark word 也叫对象标志词。对象头的信息内容是变化...
  • 自旋可以使线程在没有取得的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得继续执行。若线程依然不能获得,才会被挂起。 使用...
  • 一、偏向 大多数情况下不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向的目的是在某个线程获得之后,消除这个线程重入(CAS)的开销,看起来让这个线程得到了偏护。另外,JVM对那种会有多线程...
  • (面试题)讲下偏向、自旋量级、重量级 一、自旋 如果持有的线程能在很短时间内释放资源,那么那些等待竞争的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等...
  • Java程序员面试必备,深入浅出Java优化。偏向量级消除,粗化,自旋最全总结

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 83,509
精华内容 33,403
关键字:

则轻锁