精华内容
下载资源
问答
  • synchronized锁升级过程

    2021-01-23 20:39:57
    synchronized锁升级过程为什么要进行锁升级锁升级预备知识之对象头锁升级预备知识之CASABA问题锁升级详细过程 为什么要进行锁升级 锁升级其实就是对synchronized的优化,以前用synchronized修饰一个对象或者是方法,...

    为什么要进行锁升级

    锁升级其实就是对synchronized的优化,以前用synchronized修饰一个对象或者是方法,方法也等于是锁住对象,直接上一把操作系统层面的大锁,万一只有少量线程的话会大题小作了,如果大量线程的话又会特别消耗时间,划不来,所以要将以前的二话不说上一把大锁进行优化。
    锁升级的过程是 无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁

    锁升级预备知识之对象头

    对象在jvm中是这么布局的,分为三块,分别是对象头、实例数据和对齐填充。
    我们要学习锁升级就要先学习对象头,因为锁的升级过程就是在对象头里标志的。
    由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间。
    markword

    锁标志位 状态
    01 无锁或者偏向锁
    00 轻量级锁
    10 重量级锁
    11 GC标记

    根据上图和状态表可知,无锁状态也就是对象刚被new出来的时候,对象有HashCode区域,同时锁标志位为01,注意在对象未使用到HashCode的时候是0。偏向锁状态拥有线程ID和Epoch区域且锁标志位为01。轻量级锁有指向轻量级锁的指针且标志位为00。重量级锁有指向重量级锁的指针以及锁标志位为10。

    锁升级预备知识之CAS

    CAS全程叫做Compare And Set,比较并设置,简单的来说就是把对象当前的值拿来跟这个线程操作之前的值进行比较,如果一样说明在该线程操作期间没有其他线程对对象进行操作,所以就可以把该线程操作之后的值设置给对象,如果比较结果是不一样的说明在该线程操作期间有其他线程动了对象,那么该线程就会把操作之前的值修改为对象当前的值之后再进行操作。循环进行一直到成功设置值为止,所以它也叫做自旋锁。

    ABA问题

    假设线程1和线程2都对x进行操作,x的值为1,那么如果线程2在自旋的过程中线程1对x进行了加0的操作或者除以1的操作,那么x的值还是1,但是线程1还是对x进行了操作的,那么线程2不知道哇,1和它原来的值是一样的,但是x已经不是原来的那个x了,这就是ABA问题。
    那么如何解决ABA问题呢?加版本号。例如,每次有线程修改了引用的值,就会进行版本的更新,虽然两个线程持有相同的引用,但他们的版本不同,这样,我们就可以预防 ABA 问题了。

    锁升级详细过程

    一开始对象被创建,这个对象的状态是无锁状态,当有一个线程过来锁它的时候,这个对象会贴上这个线程的ID,Epoch是转换成这个偏向锁的时间戳。然后如果没有其他线程要来锁这个对象,这个对象的对象头就会一直贴着之前的线程的ID,当然如果从头到尾都没有第二个线程的话,也就是编译器检测到了程序中只有一个线程,会有一个锁消除的优化机制。但是如果有第二个线程过来争夺这个对象了,锁就会升级成轻量级锁。
    轻量级锁也就是进行CAS操作的自旋锁,在代码进入同步块的时候,如果对象还没有被锁定也就是状态位为 01,那么jvm首先会在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用来存储对象当前的MarkWord的拷贝。

    官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word。

    然后jvm进行CAS操作尝试将对象的MarkWord更新为指向锁记录的指针。如果更新成功,那么这个线程就拥有了该对象的锁,并且对象的MarkWord标志位转变为 00,即表示此对象处于轻量级锁定状态;如果更新失败,虚拟机首先会检查对象的MarkWord是否指向当前线程的栈帧,如果指向则说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块中执行了,否则说明这个锁对象已经被其他线程占有了,就继续CAS,默认自旋超过10次线程就会被挂起,这个次数可以改,因为线程一直自旋也是很消耗资源的。如果有两条以上的线程竞争同一个锁,那轻量级锁不再有效,要膨胀为重量级锁,锁标志变为 10,MarkWord中存储的就是指向重量级锁的指针,而后面等待的线程也要进入阻塞状态。
    synchronized的重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个时间成本会非常高。

    以上就是我对synchronized锁升级过程的学习了,如有错误,欢迎指正!

    展开全文
  • Synchronized锁升级过程

    2021-05-05 16:32:26
    Synchronized锁升级过程 锁在jdk1.6之前只有两种状态:无锁和有锁两种状态,在jdk1.6之后,对synchronized进行了优化,增加了两种状态,现在锁一共有四种状态:无锁、偏向锁、轻量级锁、重量级锁。 无锁也是一种状态...

    Synchronized锁升级过程

    锁在jdk1.6之前只有两种状态:无锁和有锁两种状态,在jdk1.6之后,对synchronized进行了优化,增加了两种状态,现在锁一共有四种状态:无锁、偏向锁、轻量级锁、重量级锁。

    无锁也是一种状态,锁的类型和状态存在对象头的MarkWord中,在申请锁、锁升级等过程中JVM都需要读取对象的MarkWord中的数据。

    偏向锁

    为什么要引入偏向锁呢?

    因为进过HotSpot的作者大量的研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低,而引入了偏向锁。

    偏向锁的升级

    当线程a访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的锁的ThreadID,以后在线程进入和退出代码块时,不需要进行CAS操作进行加锁和解锁,只需要简单测试一下对象头的MarkWord中是否存储着指向当前线程的ThreadID

    • 如果有的话,还是线程a获取锁。
    • 如果没有的话,则需要查看对象头的MarkWord中指向的线程是否存活:
      • 如果没有存活,那么锁对象被重置为无锁状态,其他线程可以竞争并将其设置为偏向锁;
      • 如果存活,需要查找该线程(MarkWord中指向的线程)的栈帧信息,如果该线程还需要继续获得锁,那就暂停该线程,撤销偏向锁,升级为轻量级锁;如果该线程不在需要获得锁,那就将锁对象状态设置为无锁状态,重新偏向新的线程。

    举个例子:

    假设有两个线程,线程a,线程b,两个线程都要执行一个同步代码块(或方法),当线程a先访问同步代码块,会在对象头和栈帧中锁记录里存储线程a的线程id,以后在线程a进入和退出代码块时,只需要对象头中记录的线程id是否是线程a的线程id,然后就可以继续获取锁;这时当线程b访问同步方法时,它也会判断对象头中存储的线程id是否和线程b的线程id相同,因为对象头存储的是线程a的id,所以肯定不相同,接着就查看线程a是否存活,我们假设线程a还没有结束,那线程a肯定还是存活的,然后就查看线程a的栈帧信息,因为线程a,b是交替执行的,所以线程a还需要继续获得锁,那这就会暂停线程a,撤销偏向锁,将锁升级为轻量级锁。

    轻量级锁

    轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转换到内核态,代价很高,如果线程刚阻塞不就锁就被释放了,那就有点得不偿失,因此这时候,就选择不阻塞线程,而是让它自旋等待锁的释放。

    轻量级锁升级为重量级锁

    线程1获取轻量级锁时,会把锁对象的对象头MarkWord复制一份到线程1的栈帧中用于存储锁记录的空间(称为DisplacedmarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录地址。

    如果线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是线程2CAS的时候,发现线程1已经把对象头换了,线程2就会使用自旋来等待线程1释放锁。

    但是如果自旋的时间太长,就会影响性能,因为自旋需要消耗CPU,所以需要对自旋的次数进行限制,可以是10次,可以是100次,当超过了自旋次数线程1仍没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3来竞争这个锁,那这个时候轻量级锁就会升级为重量级锁。重量级锁就会把除了拥有锁的线程都阻塞,防止CPU空转。

    轻量级锁一旦升级为重量级锁,就不会再降级为轻量级锁了,偏向锁升级为轻量级锁后也不能在降级为偏向锁。锁可以升级但不能降级,但是偏向锁可以被重置为无锁状态。

    synchronized的执行过程

    • 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
    • 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
    • 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
    • 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
    • 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
    • 如果自旋成功则依然处于轻量级状态。
    • 如果自旋失败,则升级为重量级锁。

    锁的优缺点的对比

    img

    参考文章:(3条消息) synchronized底层原理以及锁升级过程_Fking’s Blog-CSDN博客_synchronize底层原理及锁的升级

    (3条消息) Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级_tongdanping的博客-CSDN博客

    展开全文
  • synchronized 锁升级过程

    2020-07-24 16:33:35
    synchronized 锁升级过程就是其优化的核心:偏向锁->轻量级锁->重量级锁 class Test{ private static final Object object = new Object(); public void test(){ synchronized(object) { // do ...

    synchronized 锁升级过程就是其优化的核心:偏向锁 -> 轻量级锁 -> 重量级锁

    class Test{
        private static final Object object = new Object(); 
        
        public void test(){
            synchronized(object) {
                // do something        
            }   
        }
        
    }
    

    每个对象创建时都有各自的对象头,用来记录持有锁的状态(如下图所示32位JVM下)

    一个对象创建时:
    1.如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即后 3 位为 101,这时它的 thread、epoch、age 都为 0。

    2.偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 XX:BiasedLockingStartupDelay=0 来禁用延迟。

    3.如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即后 3 位为 001,这时它的 hashcode、 age 都为 0,第一次用到 hashcode 时才会赋值。

     

    无锁------------>偏向锁

    使用 synchronized 关键字锁住某个代码块的时候,当锁对象第一次被线程获得的时候,进入偏向状态,标记为 1 01。同时使用 CAS 操作将线程 ID 记录到 Mark Word 中,如果 CAS 操作成功,这个线程以后每次进入这个锁相关的同步块就不需要再进行任何同步操作。

    当有另外一个线程去尝试获取这个锁对象时,偏向状态就宣告结束,此时撤销偏向(Revoke Bias)后恢复到未锁定状态或者轻量级锁状态。如果从头到尾都是一个线程在使用锁,很明显偏向锁几乎没有额外开销,性能极高。

    在偏向锁状态下如果调用了对象的 hashCode,但此时偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被 撤销,而轻量级锁会在锁记录中记录 hashCode 重量级锁会在 Monitor 中记录 hashCode,所以无影响。
     

     

    偏向锁---------->轻量级锁

    一旦有第二个线程加入锁竞争,偏向锁转换为轻量级锁自旋锁)。锁竞争:如果多个线程轮流获取一个锁,但是每次获取的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程获取锁的时候,发现锁已经被占用,需要等待其释放,则说明发生了锁竞争。

    在轻量级锁状态上继续锁竞争,没有抢到锁的线程进行自旋操作,即在一个循环中不停判断是否可以获取锁。获取锁的操作,就是通过 CAS 操作修改对象头里的锁标志位。先比较当前锁标志位是否为释放状态,如果是,将其设置为锁定状态,比较并设置是原子性操作,这个 是 JVM 层面保证的。当前线程就算持有了锁,然后线程将当前锁的持有者信息改为自己。

    假如我们获取到锁的线程操作时间很长,比如会进行复杂的计算,数据量很大的网络传输等;那么其它等待锁的线程就会进入长时间的自旋操作,这个过程是非常耗资源的。其实这时候相当于只有一个线程在有效地工作,其它的线程什么都干不了,在白白地消耗 CPU,这种现象叫做忙等 (busy-waiting)。所以如果多个线程使用独占锁,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么 synchronized 就是轻量级锁,允许短时间的忙等现象。这是一种择中的想法,短时间的忙等,换取线程在用户态和内核态之间切换的开销

     

    每个线程都的栈帧都会包含一个锁记录(Lock Record)的结构,内部可以存储锁定对象的 Mark Word,让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存 入锁记录

    如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

    如果 cas 失败,有两种情况

    如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,会先进入自旋锁状态,最后进入重量级锁状态 

    如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数


     当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一,相当于踢出一条null的锁记录。

    当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

    成功,则解锁成功

    失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
     

    轻量级锁--------->重量级锁

    显然,忙等是有限度的(JVM 有一个计数器记录自旋次数,默认允许循环 10 次,可以修改)。如果锁竞争情况严重, 达到某个最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是通过 CAS 修改锁标志位,但不修改持有锁的线程 ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是上面说的忙等,即不会自旋),等待释放锁的线程去唤醒。在 JDK1.6 之前, synchronized 直接加重量级锁,很明显现在通过一系列的优化过后,性能明显得到了提升。

    JVM 中,synchronized 锁只能按照偏向锁、轻量级锁、重量级锁的顺序逐渐升级(也有把这个称为锁膨胀的过程),不允许降级。

    那具体如何升级呢

    假设当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

    这时 Thread-1 加轻量级锁失败,进入锁膨胀流程(这里我们就先忽略锁自旋) 

    即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址

    然后自己进入 Monitor 的 EntryList BLOCKED
     

    当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

    Monitor 被翻译为监视器或管程

    每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

    刚开始 Monitor 中 Owner 为 null

    当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一 个 Owner

    在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入 EntryList BLOCKED

    Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的

    图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程

    展开全文
  • 三分钟掌握synchronized锁升级过程

    synchronized锁有四种状态,无锁,偏向锁,轻量级锁,重量级锁

    锁可以升级但不能降级,但是偏向锁状态可以被重置为无锁状态

    jdk1.6之前都是重量级锁,大多数时候是不存在锁竞争的,如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入锁升级。

    1、偏向锁

    线程1获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID

    线程1获取该锁时,比较threadID是否一致 ------- 一致 -> 直接进入而无需使用CAS来加锁、解锁

    线程2获取该锁时,比较threadID是否一致 ------- 不一致 -> 检查对象的threadID线程是否还存活

    存活:代表该对象被多个线程竞争,于是升级成轻量级锁

    不存活:将锁重置为无锁状态,锁头重新标记线程2为新的threadID

    (如果线程1和线程2的执行时间刚好错开,那么锁只会在偏向锁之间切换而不会升级为轻量级锁,在使用synchronized的情况下避开了获取锁的成本,所以效率和无锁状态非常接近)

    2020-12-01
    上述删除线的结论存疑,使用ClassLayout.parseClass(A.class).toPrintable(a)打印对象锁状态测试后,发现只要有第二个线程去锁该对象,无论原线程是否存活,该对象锁都会升级成轻量级锁。
    但网上的大部分结论好像都是如果原线程销毁则偏向锁进行重偏向,很困惑。

    2、轻量级锁

    对象被多个线程竞争时,锁由偏向锁升级为轻量级锁,轻量级锁采用自旋+CAS方式不断获取锁。

    3、重量级锁

    当线程的自旋次数过长依旧没获取到锁或多个锁竞争发生在自旋阶段时,为避免CPU无端耗费,锁由轻量级锁升级为重量级锁。

    获取锁的同时会阻塞其他正在竞争该锁的线程,依赖对象内部的监视器(monitor)实现,monitor又依赖操作系统底层,需要从用户态切换到内核态,成本非常高。

    展开全文
  • 对象头与monitorMarkWord区是存在在JAVA对象头中的一个...信息2.GC信息3.HashCode(如果有调用)public class Demo1 { public static void main(String[] args) throws IOException { Object o = new Object(); S...
  • synchronized锁升级过程分析(青铜版) 摘要 本文的目的在于从主流程上说明以下问题: synchronized修饰对象的锁一共有几种状态 synchronized修饰对象锁升级的过程是怎样的 本文的研究方法是查看openjdk的官方文档...
  • 图1 synchronized锁升级过程 2 锁升级流程 synchronized锁升级的具体流程如图2所示。 图2 synchronized锁升级流程 2.1 偏向锁->轻量级锁 线程A请求synchronized对象,比较同步块对象存储的线程S ID与线程A ID...
  • java synchronized锁升级过程

    万次阅读 2020-06-10 17:38:31
    锁升级过程是怎样的呢? 主要一开始是无锁的状态,如果偏向锁未启动,当有其他线程竞争时,会升级为轻量级锁,如果偏向锁启动,则会升级为偏向锁,如果竞争激烈则会从偏向锁升级为重量级锁,如果不激烈则有偏向...
  • 哈喽,大家好,我是IT老哥,我们今天来讲讲synchronized这个,可能你们第一印象是这个太笨了,太重了,谁用谁是傻子,如果你是这样想的话,那么面试的时候基本上就是凉凉了。因为jdk1.6之后对它进行了优化,它...
  • 在JDK1.6前,synchronized只是一把重量级的锁,而jdk1.6后,实现了偏向锁,轻量锁等,引入锁升级的机制,使得synchronized更加高效,性能更好,现在就来讲讲synchronized升级的过程。 对象头 首先我们要先知道什么是...
  • 注:synchronized锁流程如下 第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁” 第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据...
  • synchronized锁升级过程和实现原理

    千次阅读 2020-07-04 18:42:25
    一.synchronized升级过程 在最开始JDK1.0,1.2的时候,synchronized就是重量级锁,后来JDK对synchronized进行了一系列优化,这个优化就是有个升级过程。这个升级过程被markword清晰地进行了记录。 整个锁升级的...
  • 2.轻量级和重量级升级 3. 消除 像 StringBuffer 在方法体内部的话,因为虚拟机栈是线程所有的,方法对应虚拟机栈中的栈帧,是线程安全的,会JVM会将当前的进行消除处理,处于无锁的状态 4.Monitor ...
  • synchronized锁升级过程(无锁->偏向锁->轻量级锁->重量级锁) 锁的级别从低到高:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 首先,我们来看一下Java对象的组成结构:对象头+成员变量 其中Mark ...
  • Synchronized锁升级过程:

    2020-08-27 09:58:57
    1.如果一开始是无锁状态,那么第一个线程获取索取的时候,判断是不是无锁状态,如果是无锁(001),就通过CAS将mark word里的部分地址记录为当前线程的ID,同时最后倒数第...而无需每次都进行CAS,当然这个理想过程是没
  • 1.简介 在Java高并发系统中,我们常常需要使用多线程技术来提高...修饰类方法(static修饰的方法):以类对象为,进入同步代码块前需要获得当前类对象的 修饰代码块:需要指定一个对象(既可以是实例对象,也..

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 631
精华内容 252
关键字:

synchronized锁升级过程