美文网首页
25 深入理解并发锁

25 深入理解并发锁

作者: 滔滔逐浪 | 来源:发表于2020-07-23 07:43 被阅读0次

    Synchronized 锁升级
    重量级: 只要有一个线程获取到锁的情况下,其他的线程都会变为阻塞的状态。效率非常低。
    偏向锁; 偏向一个线程;
    轻量锁:

    偏向锁---》 轻量锁--》 短暂自旋--》重量级锁(线程阻塞)

    偏向锁运行:

    
    package com.taotao.metithread;
    
    import org.openjdk.jol.info.ClassLayout;
    
    /**
     *@author tom
     *Date  2020/7/21 0021 8:18
     *偏向锁运行
     */
    public class Test007  extends Thread{
        private  Object object=new Object();
    
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                synchronized (object){
                    System.out.println(Thread.currentThread().getName()+",偏向锁运行");
                    System.out.println(ClassLayout.parseInstance(object).toPrintable());
                }
            }
        }
    
        public static void main(String[] args) {
            new Test007().start();
        }
    }
    
    
    

    同步代码块一直都是同一个线程获取锁 偏向锁
    多个线程间隔的形式获取锁 轻量级

    多个线程竞争锁,同时竞争锁 重量级锁

    偏向锁,偏袒一个线程
    当一个线程获取锁的时候,会在锁的对象头中记录线程id;moniter,下次你还是线程重复获取锁的时候就不会在做cas加锁和解锁操作。

    image.png

    如果java对象头里没有关联线程id,说明根本就没有线程获取锁
    1, 线程T1检查我们java对象头中是否有关联当前线程t1;
    2, 如果没有的情况下,使用cas修改mark标记为当前线程t1;

    3, 如果使用cas修改markword 成功的情况下,获取锁,如果修改失败的情况下,则说明当前markword 已经被另外线程更改过,等于获取锁失败。

    4,如果使用cas修改markword成功情况下,markword 标记当前线程t1,同时修改markword 中 的偏向锁标记为1;

    当一个线程访问同步代码块并获取锁时;会在 对象头 和 自己的栈帧中 的锁记录中记录存储偏向锁的线程ID;以后该线程再次进入同步代码块时不再需要CAS来加锁和解锁;
    只需要简单测试一下对象头的 mark word 中偏向锁线程的ID是否是当前线程ID;
    如果成功;表示线程已经获取到锁直接进入代码块运行,
    如果测试失败;检查当前偏向锁字段是否为0;
    如果为0(表示线程还不是偏向锁,是无锁状态);采用CAS操作将偏向锁字段设置为1;并且更新自己的线程ID到mark word 字段中;
    如果为1;表示此时偏向锁已经被别的线程获取;则此时线程不断尝试使用CAS获取偏向锁;
    或者将偏向锁撤销,升级成轻量级锁; (升级概率较大)

    偏向锁原理
    当锁对象第一次被线程获取的时候,虚拟机会把对象头中的标志位设置为“01”、把偏向模式设置为“1”,表示进入偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word中。若CAS操作成功,持有偏向锁的线程以后每次进入到这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。

    开启偏向锁

    偏向锁在Java 1.6之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用
    -XX:BiasedLockingStartupDelay=0
    参数关闭延迟,如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过
    XX:-UseBiasedLocking=false
    参数关闭偏向锁。

    package com.taotao.metithread;
    
    import org.openjdk.jol.info.ClassLayout;
    
    /**
     *@author tom
     *Date  2020/7/21 0021 8:18
     *偏向锁运行 偏向一个锁
     * 交替进行线程 轻量锁
     */
    public class Test007  extends Thread{
        private  Object object=new Object();
    
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                synchronized (object){
                    System.out.println(Thread.currentThread().getName()+",偏向锁运行");
                    System.out.println(ClassLayout.parseInstance(object).toPrintable());
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
    
         /*   Test007 test1=new Test007();
            test1.start();
            Thread.sleep(3000);
    
    
            Test007 test2=new Test007();
    
            test2.start();
            Thread.sleep(3000);*/
             new Test007().start();
        }
    }
    
    
    
    image.png

    偏向锁撤销的过程: 如果当前线程一直持有锁为偏向锁的情况下,这时候突然有另外的一个线程竞争java锁对象,这时候偏向锁会开始撤销。撤销后有可能会改成轻量锁,或者是重量锁。

    
    package com.taotao.metithread;
    
    import org.openjdk.jol.info.ClassLayout;
    
    /**
     *@author tom
     *Date  2020/7/21 0021 8:18
     *偏向锁运行 偏向一个锁
     * 交替进行线程 轻量锁
     */
    public class Test007  extends Thread{
        private static   Object object=new Object();
    
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                synchronized (object){
                    System.out.println(Thread.currentThread().getName()+",轻量锁运行");
                    System.out.println(ClassLayout.parseInstance(object).toPrintable());
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
    
         /*   Test007 test1=new Test007();
            test1.start();
            Thread.sleep(3000);
    
    
            Test007 test2=new Test007();
    
            test2.start();
            Thread.sleep(3000);*/
             new Test007().start();
            new Test007().start();
            new Test007().start();
            new Test007().start();
        }
    }
    
    
    
    

    轻量锁: 多个线程间隔形式获取锁对象。
    如果同步代码块执行的时间非常快,可能几乎不算。
    T1获取锁的时候,会非常快释放锁,在唤醒t2 线程获取锁。

    自旋 非常消耗cpu资源。

    栈帧: 方法的执行 空间。
    T1线程复制对象头中markword(hashcode,偏向锁,gc等待)到栈帧空间。

    image.png

    偏向锁撤销
    偏向锁使用一种等待竞争出现才释放锁的机制;当有其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放偏向锁, 偏向锁的撤销开销较大;需要等待线程进入全局安全点 safepoint
    1.偏向锁的撤销必须等待全局安全的点
    2.暂停拥有偏向锁的线程,判断锁对象是否被锁定
    3.撤销偏向锁,将对象头中的标记01恢复为00 轻量级锁或者重量级
    偏向锁的好处
    偏向锁适合于只有一个线程重复获取锁的时候,没有任何的竞争,从而提高锁的效率。

    轻量级锁
    当多个线程在间隔的方式竞争我们的锁对象,短暂结合自旋控制。

    当前我们的线程的栈帧创建存储锁记录的空间,并且将该对象头中markWord
    复制到该锁记录中,然后线程尝试使用Cas将对象头中markWord替换指向锁的记录指针。
    如果成功下,当前获取锁,如果失败的情况下,说明没有获取锁,尝试短暂的自旋获取锁。

    轻量锁竞争
    当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁 。
    1.判断当前对象是否处于无锁状态(hashcode、0、01),若是,则JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word);否则执行步骤(3);

    1. JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指正,如果成功表示竞争到锁,则将锁标志位变成00(表示此对象处于轻量级锁状态),执行同步操作;如果失败则执行步骤(3);
    2. 判断当前对象的Mark Word是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态;
      轻量锁释放
      轻量级锁的释放也是通过CAS操作来进行的,主要步骤如下:
    3. 取出在获取轻量级锁保存在Displaced Mark Word中的数据;
    4. 用CAS操作将取出的数据替换当前对象的Mark Word中,如果成功,则说明释放锁成功,否则执行(3);
    5. 如果CAS操作替换失败,说明有其他线程尝试获取该锁,则需要在释放锁的同时需要唤醒被挂起的线程。

    什么是栈帧,也就是执行我们的方法。

    重量锁:

    总结:
    1,偏向锁:只有一个线程的情况下,可以使用轻量级减少cas枷锁和释放锁的操作。注意: 如果多个线程同时访问锁的情况下。偏量锁撤销为轻量锁或者是重量锁。
    2,轻量锁: 多个线程间隔或者短暂同时竞争锁的情况下:不会导致我们当前的线程阻塞。如果没有获取到锁的情况下会采用自旋的形式重复的获取锁,但是会非常的消耗cpu的资源。如果多次重复锁失败则变为重量级锁。
    应用场景: 同步代码块里面的代码执行时间是非常快的情况下。
    3,重量锁:
    在没有获取到锁的线程会变为阻塞的状态,效率极低。
    应用场景: 线程不会通过自旋,不会消耗cpu适合于同步代码块执行时间非常长的。

    锁的消除
     锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。锁削除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。

    package com.taotao.metithread;
    
    /**
     *@author tom
     *Date  2020/7/23 0023 7:21
     *锁的消除
     */
    public class Test008 {
        public static void main(String[] args) {
            addString("1","2","3");
        }
    
        private static String addString(String s1,String s2,String s3) {
        return new StringBuffer().append(s1).append(s2).append(s3).toString();
        }
    }
    
    
    
    

    在多线程情况下会产生多个不同的this 锁,有可能编译器会做优化,消除synchronized 锁。

    synchronized 使用的时候注意事项(优化)
    1,减少synchronized 同步代码块范围,减少同步代码执行的时间,只会使用偏向锁或者轻量锁。
    2, 将锁的粒度拆分细点
    hashTable
    3,锁一定要做读写分离;

    相关文章

      网友评论

          本文标题:25 深入理解并发锁

          本文链接:https://www.haomeiwen.com/subject/lzyukktx.html