zoukankan      html  css  js  c++  java
  • 多线程锁的升级(膨胀)原理

    什么是锁升级(锁膨胀)?

      JVM优化synchronized的运行机制,当JVM检测到不同的竞争状态时,就会根据需要自动切换到合适的锁,这种切换就是锁的升级。升级是不可逆的,也就是说只能从低到高,也就是偏向-->轻量级-->重量级,不能够降级

      锁级别:无锁->偏向锁->轻量级锁->重量级锁

    java对象头

      synchronized用的锁存在Java对象头里,Java对象头里的Mark Word默认存储对象的HashCode、分代年龄和锁标记位。在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。32位JVM的Mark Word可能变化存储为以下5种数据:

    CAS

      compareAndSwap,比较并替换,是一种实现并发算法时常用到的技术CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B;比如你要操作一个变量,他的值为A,你希望将他修改为B,这期间不会进行加锁,当你在修改的时候,你发现值仍旧是A,然后将它修改为B,如果此时值被其他线程修改了,变成了C,那么将不会进行值B的写入操作,这就是CAS的核心理论,通过这样的操作可以实现逻辑上的一种“加锁”,避免了真正去加锁。

    实例自增代码:

    public final int incrementAndGet() {
            for (; ; ) {                    //自旋
                int current = get();        //旧值
                int next = current + 1;     //新值
                if (compareAndSet(current, next))  //如果旧的预期值与内存中的值一致,那么将新值进行赋值,否则继续自旋
                    return next;
            }
    
        }

     偏向锁

      当一个线程访问同步块并获取锁时,会在对象头和栈帧的锁记录里存储偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需测试Mark Word里线程ID是否为当前线程。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要判断偏向锁的标识。如果标识被设置为0(表示当前是无锁状态),则使用CAS竞争锁;如果标识设置成1(表示当前是偏向锁状态),则尝试使用CAS将对象头的偏向锁指向当前线程,触发偏向锁的撤销。偏向锁只有在竞争出现才会释放锁。当其他线程尝试竞争偏向锁时,程序到达全局安全点后(没有正在执行的代码),它会查看Java对象头中记录的线程是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程,撤销偏向锁,升级为轻量级锁,如果线程1不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

      偏向锁应用的场景是一个同步代码块只有一个线程频繁访问,使用偏向锁,就不需要频繁使用CAS获取锁和释放锁,只需要简单判断对象头中记录的偏向锁的线程ID是否是当期线程的就可以了,所以偏向锁在这种场景下可以大大提升效率。

    轻量级锁

      线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的MarkWord复制到锁记录中,即Displaced Mark Word。然后线程会尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁。如果失败,表示其他线程在竞争锁,当前线程使用自旋来获取锁。当自旋次数达到一定次数时,锁就会升级为重量级锁。

      当线程存在竞争时,偏向锁的效率就会降低,因为当多条线程竞争同一个偏向锁时,会频繁产生偏向锁的撤销,所以此时应该升级为轻量级锁,轻量级锁当线程竞争锁失败时,线程不会阻塞进入自旋,继续获取锁,当竞争非常激烈时,持续自旋而获取不到锁会消耗大量CPU资源,此时就会升级为重量级锁,重量级锁当获取锁失败线程会阻塞,重量级锁的缺点是线程上下文会频繁的切换。

    synchronized优化-锁消除

      消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如下StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。

    参考链接:synchronized实现原理及锁的升级过程  

           Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)

  • 相关阅读:
    深拷贝(deep clone)与浅拷贝(shallow clone)
    wait和notify
    Java实现简单RPC框架(转)
    Eclipse 搭建Struts2
    手写HashMap实践
    JVM 类加载器ClassLoader源码学习笔记
    Java 内存模型学习笔记
    Struts2 入门笔记
    struts2 拦截器
    Btrace 拦截时机
  • 原文地址:https://www.cnblogs.com/jxxblogs/p/11890563.html
Copyright © 2011-2022 走看看