Synchronized的由浅入深

一. 使用

synchronized可用来修饰实例方法,静态方法和代码块,其作用是为了保证被修饰的方法或代码块在同一时间内,只有一个线程可以执行。那么我们先看看如何使用,因重点不是介绍使用,会快速而简单的介绍下。

public class TestSynchronized {

    private static int count = 0;

    public synchronized void increase() {
        for (int i = 0; i < 100000; i++) {
            count++;
        }
    }

    public static synchronized void staticIncrease() {
        for (int i = 0; i < 100000; i++) {
            count++;
        }
    }

    public void thisIncrease() {
        synchronized (this) {
            for (int i = 0; i < 100000; i++) {
                count++;
            }
        }
    }

    public void classIncrease() {
        synchronized (TestSynchronized.class) {
            for (int i = 0; i < 100000; i++) {
                count++;
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        // synchronized修饰实例方法时,锁对象为实例对象,不同的实例对象互不干扰
        test1();
        test2();
        // synchronized修饰静态方法时,锁对象为类对象,不受实例对象影响
        test3();

        // synchronized 修饰代码块时,如果用this,作为锁对象,锁的就是实例对象。和实例方法类似
        test4();
        test5();

        //synchronized 修饰代码块时,如果用class作为锁对象,锁的就是类对象。使用多个实例对象调用,
        // 也都是同一把锁
        test6();
        test7();
    }

    private static void test7() throws InterruptedException {
        TestSynchronized testSynchronized1 = new TestSynchronized();
        TestSynchronized testSynchronized2 = new TestSynchronized();
        Thread thread1 = new Thread(() -> testSynchronized1.classIncrease());
        Thread thread2 = new Thread(() -> testSynchronized2.classIncrease());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count ->" + count);
    }

    private static void test6() throws InterruptedException {
        TestSynchronized testSynchronized = new TestSynchronized();
        Thread thread1 = new Thread(() -> testSynchronized.classIncrease());
        Thread thread2 = new Thread(() -> testSynchronized.classIncrease());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count ->" + count);
    }

    private static void test5() throws InterruptedException {
        TestSynchronized testSynchronized1 = new TestSynchronized();
        TestSynchronized testSynchronized2 = new TestSynchronized();
        Thread thread1 = new Thread(() -> testSynchronized1.thisIncrease());
        Thread thread2 = new Thread(() -> testSynchronized2.thisIncrease());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count ->" + count);
    }

    private static void test4() throws InterruptedException {
        TestSynchronized testSynchronized = new TestSynchronized();
        Thread thread1 = new Thread(() -> testSynchronized.thisIncrease());
        Thread thread2 = new Thread(() -> testSynchronized.thisIncrease());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count ->" + count);
    }

    private static void test3() throws InterruptedException {
        TestSynchronized testSynchronized1 = new TestSynchronized();
        TestSynchronized testSynchronized2 = new TestSynchronized();
        Thread thread1 = new Thread(() -> testSynchronized1.staticIncrease());
        Thread thread2 = new Thread(() -> testSynchronized2.staticIncrease());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count ->" + count);
    }

    private static void test2() throws InterruptedException {
        TestSynchronized testSynchronized1 = new TestSynchronized();
        TestSynchronized testSynchronized2 = new TestSynchronized();
        Thread thread1 = new Thread(() -> testSynchronized1.increase());
        Thread thread2 = new Thread(() -> testSynchronized2.increase());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count ->" + count);
    }

    private static void test1() throws InterruptedException {
        TestSynchronized testSynchronized = new TestSynchronized();
        Thread thread1 = new Thread(() -> testSynchronized.increase());
        Thread thread2 = new Thread(() -> testSynchronized.increase());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count ->" + count);
    }
}

1. 修饰实例方法

increase()方法是类的实例方法,那么我们看下test1(), test2()方法的执行结果

count ->200000
count ->395058

结论:实例方法:锁的是实例对象,在不同线程用同一个实例对象才能保证synchronized的作用

2. 修饰静态方法

staticInrease()方法是类的静态方法,我们先看下test3()方法执行的结果(基本不会使用实例对象调用静态方法,此处只是为了测试而已)

count ->200000

结论:静态方法:锁的是类对象(class),在不同线程用不同的实例对象,也能保证synchronized的作用,因为class对象在方法区中只有一份

3. 修饰代码块

修饰代码块,分为两种情况,一中是thisIncrease()方法,synchronized(实例对象),this是当前类的实例对象;另一种是classIncrease()方法,synchronized(类)。我们先看第一种,执行test4(),test5()方法的结果

count ->200000
count ->317112

结论:修饰代码块时,synchronized(实例对象):锁的是实例对象,与修饰实例方法一致

再看第二种,执行test6(),test7()的结果:

count ->200000
count ->400000

结论:修饰代码块时,synchronized(类对象):锁的类对象,与修饰静态方法一致

4.用法总结

synchronized在使用上分为三种类型,分别是修饰实例方法,静态方法,代码块;从锁的角度来看,分为两种,分别是锁实例对象和锁类对象

二.synchronized在class文件中的实现

我们就直接把上面的测试代码的class文件反编译出来看看,太长了,我们只截取需要的信息

//  increase在flags有一个ACC_SYNCHRONIZED标记位
public synchronized void increase();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED

 // staticIncrease同理也是有一个 ACC_SYNCHRONIZED标记位      
 public static synchronized void staticIncrease();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
 
 // thisIncrease方法没有ACC_SYNCHRONIZED的标记位了
 // 但是可以观察到monitorenter,monitorexit 这看起来就像是锁操作了。
 // classIncrease与此类似,就不多说       
   public void thisIncrease();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: iconst_0
         5: istore_2
         6: iload_2
         7: ldc           #2                  // int 100000
         9: if_icmpge     26
        12: getstatic     #3                  // Field count:I
        15: iconst_1
        16: iadd
        17: putstatic     #3                  // Field count:I
        20: iinc          2, 1
        23: goto          6
        26: aload_1
        27: monitorexit
        28: goto          36
        31: astore_3
        32: aload_1
        33: monitorexit
        34: aload_3
        35: athrow
        36: return
       

结论:synchronized在修饰方法时,在class文件中是通过ACC_SYNCHRONIZED标记的,修饰代码块时,是通过monitorenter和monitorexit指令来标记的

ACC_SYNCHRONIZED

synchronized修饰方法时是隐式的,在调用方法时,会去检查运行时常量池的method_info中的ACC_SYNCHRONIZED标记位,如果存在的话,执行线程会先获取monitor锁(英文是:enter the monitor),执行方法,释放monitor锁(exit the monitor)。若同步方法抛出异常,会在抛出之前先释放monitor锁。

MonitorEnter&MonitorExit

每一个对象都有一个对应的monitor,一旦这个monitor被拥有之后,就相当于被锁住。当线程执行monitorenter指令时,就尝试获取对应的monitor。

  1. 每个monitor维护一个被拥有次数的计数器,没有时是0,当一个线程获取monitor后,计数器自增为1。
  2. 当前线程再次获取此monitor时,计数器加1;当不同线程尝试获取此monitor时,会被阻塞。
  3. 当同一个线程释放monitor时,计数器减1,当计数器为0时,monitor被释放,其他线程可尝试获取。
  4. 细心一点的话,可以发现前面方法的字节码中有两个monitorexit指令,其作用是为了保证在代码发生异常时,也可以退出当前锁状态。

结论:字节码角度上,synchronized的实现只区分修饰方法和修饰代码块,分别对应ACC_SYNCHRONIZED标记位和MonitorEnter&MonitorExit指令,而从这两个底层分析,其实都是依靠一个Monitor对象

三. Monitor&重量级锁?

monitor叫做管程,在JVM中的实现是ObjectMonitor,我们来看下ObjectMonitor的实现原理。

先看下ObjectMonitor的数据结构,代码是c++的文件,我这里是从网上截取下来的。

_owner   //拥有当前锁的线程
_count   //_owner线程获取锁的次数,synchronized是可重入锁,也就是在锁的代码块中仍可以加锁
_WaitSet // 存放处于wait状态的线程队列
_EntryList 
CXQ(ContentionList)  

运行流程如下图(来源于网络):此流程感觉比网络上博客写得更为详细,感兴趣的可以在文章底部查看链接访问原文。

在这里插入图片描述

这里也是简述线程获取monitor的流程:

  1. 想要获取monitor的线程通过CAS尝试获取锁,若失败的话,尝试用自旋的方式获取,若仍获取失败,进入CXQ,并阻塞;
  2. 竞争成功,将 _owner赋值为此线程;
  3. 若在代码中调用了wait()方法,则此线程进入_waitSet,此时 _EntryList中其他线程可以获取monitor;
  4. 若在代码中调用了notify()/notifyAll()方法,则会唤醒 _waitSet中的线程,放入Entry或CXQ尝试竞争锁;
  5. 当同步代码执行结束后,将_owner设为null,退出。让其他线程竞争。

结论:上述流程就是重量级锁,在JDK1.6以前synchronized的实现就是通过重量级锁实现的

其缺点是性能很差,其原因是monitor的底层实现是靠操作系统的Mutex Lock来实现,而这涉及到用户态和内核态之间的切换(当执行代码时是用户态,当遇到synchronized时就需要切换到内核态),而这切换过程消耗时间。

在JDK1.6及以后,JVM为了提高synchronized的效率,对其做了很多优化,其中最重要的就是锁升级,这才说到我们的重点。

四.Synchronized的优化-锁升级

我们在前面学习了synchronized在class文件中的实现,在JVM和操作系统中重量级锁ObjectMonitor的实现,也得知其性能很差,那么JVM的开发者就对此做了优化,那就是锁升级策略。在说锁升级策略前,必须要学习的是对象在堆中的布局结构。因为其布局结构中就保存了锁的相关信息。

对象在内存的数据结构

对象在内存的数据基本如下图,基本分为三个部分,分别是对象头信息,实例数据,对齐字节。
在这里插入图片描述

  • 对象头信息分为Mark word和Class Pointer。Class Pointer就是指向方法区中的类对象。
  • Instance Data就是实例对象的实例变量的数据。
  • 对齐字节是JVM要求对象起始地址必须是 8 字节的整数倍,若前面不是8字节的倍数,则就会加上对齐字节。

Mark Word

把Mark Word单独拎出来的原因是锁信息就是存储在Mark Word里的。

Mark Word的长度:在32位JVM中是32bit,在64位JVM中是64bit。

我们来写个代码看下

    public static void main(String[] args) {
        Object object = new Object();
        //ClassLayout是jol-cli jar包内的类,openJdk工具                                    :
        // 地址:https://mvnrepository.com/artifact/org.openjdk.jol/jol-cli
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }

执行结果:

在这里插入图片描述

我的JVM是64位,所以前8个字节是Mark Word,8-12字节是class pointer(原本也是8字节,JVM默认开启压缩优化,变成4字节,参数为-XX:+UseCompressedOops),12-16字节就是补齐字段。因为我仅仅是new Object,所以没有实例数据。

那我们现在来看下Mark Word里面的具体内容,Mark Word中不同的标记和Synchronized的锁的对应关系,如下图(来源于网络),32位和64位的存储内容的结构基本一致,仅仅是大小有些区别。我们后续在测试过程中会对应到表中的标记位来确定当前的锁情况。
在这里插入图片描述
在这里插入图片描述

锁概念介绍

锁状态分为无锁,偏向锁,轻量级锁,重量级锁。升级过程也就是:无锁 -> 偏向锁 -> 轻量级锁->重量级锁。

无锁:当第一次创建对象,且JVM未开启偏向锁时,且未对此对象进行加锁操作时的状态

偏向锁:当第一次创建对象,JVM开启偏向锁时,尚未对此对象进行加锁操作,叫做匿名偏向(头信息中线程id=0);当对此对象进行加锁操作时,使用CAS操作把使用当前线程id赋值到mark word中,此时是偏向锁开启状态

轻量级锁:当另外一个线程同时请求此对象锁时,偏向锁会进行锁撤销,在safe point时,查看持有锁的偏向线程是否存活,如果存活且还在同步块中,则升级为轻量级锁,原偏向线程继续持有,当前线程进入锁升级逻辑。如果偏向线程不存活或不在同步块中,则改为无锁状态,再升级为轻量级锁。

重量级锁:当线程1持有轻量级锁时,线程2也来请求获取锁,线程1的持有,线程2会请求失败,此时线程2会进入自旋锁状态。当线程2 自旋次数超过一定次数,或者此时线程3也来请求获取锁,轻量锁就会膨胀为重量级锁。

无锁&偏向锁

   /**
     * 偏向锁延迟: 因在JVM启动时需要创建许多许多后台线程,GC,VM Thread等
     * 会有许多的同步操作,所以在JVM启动是延迟开启偏向锁的,默认是延迟4s。
     * -XX:BiasedLockingStartupDelay=4000
     */
    private static void testPXDelay() {
        //无锁状态
        Object object = new Object();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //匿名偏向状态
        Object object1 = new Object();
        System.out.println(ClassLayout.parseInstance(object1).toPrintable());
    }

执行结果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION        VALUE
      0     4        (object header)    01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)    00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)    28 0f df 17 (00101000 00001111 11011111 00010111) (400494376)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION        VALUE
      0     4        (object header)    05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)    00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)    28 0f df 17 (00101000 00001111 11011111 00010111) (400494376)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

object在创建时JVM是关闭偏向锁的,未对object做锁操作,所以其是无锁状态,状态就是001。

00000001 00000000 00000000 00000000
00000000 00000000 00000000 00000000

红色部分为锁状态,绿色部分是对象分代年龄,黄色部分是hashcode(其为0的原因是没有调用过hashcode方法,若调用过则赋值)

测试下hashcode:

        Object object = new Object();
        System.out.println(Long.toBinaryString(object.hashCode()));
        System.out.println(ClassLayout.parseInstance(object).toPrintable());

//执行结果(只截取需要的信息了,打印结果可以看到信息是反着排序的):
        hashcode->10101010000001110000110011101
            
        00000001 10011101 11100001 01000000  
        00010101 00000000 00000000 00000000    
偏向锁延迟

偏向锁延迟: 因在JVM启动时需要创建许多许多后台线程,GC,VM Thread等,会有许多的同步操作,所以在JVM启动是延迟开启偏向锁的,默认是延迟4s。设置参数: -XX:BiasedLockingStartupDelay=4000

object1是在JVM启动5秒后创建的,此时JVM是开启了偏向锁的。所以object1是开启偏向锁的,锁状态为101。但因为其未被其他线程获取锁,所以是匿名偏向状态。那我们用一个线程来尝试锁一下。

Thread thread = new Thread(() -> {
            synchronized (object1) {
                System.out.println(ClassLayout.parseInstance(object1).toPrintable());
            }
        });
        thread.start();
        System.out.println("--->" + Long.toBinaryString(thread.getId()));

执行结果:

--->1100
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION       VALUE
      0     4        (object header)   05 a0 9a 59 (00000101 10100000 10011010 01011001) (1503305733)
      4     4        (object header)   00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)   28 0f d0 17 (00101000 00001111 11010000 00010111) (399511336)
     12     4        (loss due to the next object alignment)

结果分析:当我们未锁object1时,mark word中除了锁状态以外都是0,当我们synchronized(object1)后,mark word就有值了。表示存储了线程id,但是呢我发现和我通过thread.getId()的值是不一样的。这不太清楚是何原理。先放在这里吧。

总结:以上分析了无锁状,匿名偏向,偏向锁三种状态。其升级过程:当JVM未开启偏向锁时,创建对象,即是无锁状态;开启偏向锁后,创建对象,即是匿名偏向状态;一旦某个线程尝试获取锁时,便会尝试把线程Id通过CAS操作贴到对象的mark word里,成功后便是偏向锁状态

原因分析:为何会有偏向锁呢?是因为HotSpot开发者发现在很多情况下,加synchronized的方法或代码其实只会有一个线程访问。其实无需从用户态切换到内核态(重量级锁),那么就产生了偏向锁。举个列子,比如我们使用stringbuffer,在下面的代码情况下,其实完全无需申请锁。

   private void testStringBuffer() {
       StringBuffer stringBuffer = new StringBuffer();
       stringBuffer.append(1).append(2);
        System.out.println(stringBuffer.toString());
    }

轻量级锁

轻量级锁适应的场景是线程交替执行的情况,如果两个线程同时竞争同一把锁的话,会导致轻量级锁膨胀为重量级锁。

先看产生轻量级锁的情况,测试代码

    private static void testQL() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object object = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (object){
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
            try {
                //thread1退出同步代码块,且没有死亡
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (object){
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
        });
        thread1.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //线程1执行结束之后,线程2开始执行
        thread2.start();
    }

执行结果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION   VALUE
      0     4   (object header)    05 88 87 5b (00000101 10001000 10000111 01011011) (1535608837)
      4     4   (object header)    00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4   (object header)    28 0f b4 17 (00101000 00001111 10110100 00010111) (397676328)
     12     4   (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION   VALUE
      0     4   (object header)    a0 f2 5a 5d (10100000 11110010 01011010 01011101) (1566241440)
      4     4   (object header)    00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4   (object header)    28 0f b4 17 (00101000 00001111 10110100 00010111) (397676328)
     12     4   (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果分析:在线程1执行时,仍然是偏向锁,当线程1执行完成之后,线程2执行,偏向锁就会升级成轻量锁。

两个线程同时竞争的情况

    private static void testZL() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object object = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (object) {
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (object) {
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
        });
        //线程1,2同时执行
        thread1.start();
        thread2.start();
    }

执行结果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           1a ed 49 58 (00011010 11101101 01001001 01011000) (1481239834)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           28 0f ab 17 (00101000 00001111 10101011 00010111) (397086504)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           1a ed 49 58 (00011010 11101101 01001001 01011000) (1481239834)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           28 0f ab 17 (00101000 00001111 10101011 00010111) (397086504)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果分析:object的锁状态直接就是10了,故当两个线程直接竞争同一把锁时,就直接升级为重量级锁。

锁升级流程

偏向锁流程图:

在这里插入图片描述

轻量级锁流程图:

在这里插入图片描述

五. CAS

在前面的过程中,不断提到CAS操作。那我们来简单学习一下CAS操作是什么吧。CAS: Compare And Swap。是为了防止多线程修改同一数据的优化操作。为了避免多线程的同时修改,在每次写入数据的时候,都会拿原来的值和内存中的值对比一下,如果相同,认为没改过,那就可以把计算后的数据写入到当前内存中。如果不同,则认为修改过,那么重新读取当前值,再次重复上述操作。这就是CAS。

ABA问题:当拿原来的值和内存的值比较时,可能存在一种情况是许多线程修改过此数据,在多次修改后变回来原来的值,在比较时发现值相同,但实际上是修改过的。这就是ABA问题。解决方案是添加一个版本号,每次改动都修改一次版本号。在比较时不仅比较值,也比较版本号。

流程图如下:
在这里插入图片描述

六. 总结

一个小小的关键字synchronized,其背后原理和实现竟如此的复杂。不得不佩服JVM的设计者和开发者。我也是边学习边记录,可能存在理解错误,请批判性学习。

使用

修饰静态方法,实例方法,代码块(synchronized实例对象或类对象)。

Class文件

  1. 方法:在常量池的方法信息中添加ACC_SYNCHRONIZED标记
  2. 代码块:在执行指令中添加monitorenter, monitorexit指令,其中会有两个monitorexit,是为了保证在同步代码块抛出异常时,可以退出锁的占用。

锁升级

JDK1.6以前synchronized都是重量级锁,其每次锁操作都需要从用户态切换到核心态,效率极低。

为了优化synchronized性能,引入无锁,偏向锁,轻量级锁,重量级锁的状态。其区分不同的锁是依靠实例对象对象头中的mark word。

无锁:在JVM关闭偏向锁状态,或在JVM延迟开启偏向锁的时间段内创建的对象,且没有synchronized操作,是无锁状态;

偏向锁: 其引入原因是多数情况下只有一个线程执行此同步块代码,在JVM开启偏向锁时,创建的对象首先匿名偏向状态。当线程A将线程id贴到对象的mark word上,表明此线程A占有了该对象的偏向锁。

轻量级锁:虽然存在多线程竞争,但同步代码执行速度很快,或基本处于多个线程轮流执行的场景时,使用轻量级锁。当线程B请求相同对象的锁时,发现线程A的id已经贴在mark word上时,就会升级为轻量级锁(还有一些其他场景,在前面已经提过了,这里主要说锁升级的流程)。实现方式:在当前线程的栈帧中插入lock record,赋值mark word,并尝试将mark word中的指针指向lock record,赋值成功的话,则获得轻量锁;重入时,创建一个空的lock record,解锁即弹出lock record。

自旋锁:自旋锁就是轻量级锁,当未获得轻量级锁的线程会进入自旋操作,此时就是自旋锁。

重量级锁:当线程自旋次数超过一定次数或其他线程也来竞争时轻量级锁时,就会锁膨胀为重量级锁。


参考和引用博客:

https://blog.csdn.net/javazejian/article/details/72828483
https://blog.csdn.net/qq_42914528/article/details/113777629
https://www.jianshu.com/p/5c4f441bf142
https://www.jianshu.com/p/32e1361817f0
https://segmentfault.com/a/1190000038147616
https://www.cnblogs.com/zhengbin/p/6490953.html
https://blog.csdn.net/tongdanping/article/details/79647337
https://www.jianshu.com/p/d61f294ac1a6

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值