BlazeMaple BlazeMaple
首页
  • 基础知识

    • Java的基本数据类型
    • Java中的常用类String
    • Java中的异常
    • Java中的注解
    • Java中的反射机制
    • Java中的泛型
    • Java为什么是值传递
  • 集合框架

    • Java集合核心知识总结
    • HashMap的7种遍历方式
    • 源码分析
  • Java新特性

    • Java8新特性
  • IO流

    • Java基础IO总结
    • Java IO中的设计模式
    • Java IO模型
    • IO多路复用详解
  • 并发编程

    • 并发编程基础总结
  • JVM

    • JVM基础总结
  • MySQL

    • MySQL核心知识小结
    • MySQL 45讲
  • Redis

    • Redis核心入门知识简记
  • Spring
  • SpringCloud Alibaba
  • 开发工具

    • Git详解
    • Maven详解
    • Docker详解
    • Linux常用命令
  • 在线工具

    • json (opens new window)
    • base64编解码 (opens new window)
    • 时间戳转换 (opens new window)
    • unicode转换 (opens new window)
    • 正则表达式 (opens new window)
    • md5加密 (opens new window)
    • 二维码 (opens new window)
    • 文本比对 (opens new window)
  • 学习资源

    • 计算机经典电子书PDF
    • hot120
GitHub (opens new window)
首页
  • 基础知识

    • Java的基本数据类型
    • Java中的常用类String
    • Java中的异常
    • Java中的注解
    • Java中的反射机制
    • Java中的泛型
    • Java为什么是值传递
  • 集合框架

    • Java集合核心知识总结
    • HashMap的7种遍历方式
    • 源码分析
  • Java新特性

    • Java8新特性
  • IO流

    • Java基础IO总结
    • Java IO中的设计模式
    • Java IO模型
    • IO多路复用详解
  • 并发编程

    • 并发编程基础总结
  • JVM

    • JVM基础总结
  • MySQL

    • MySQL核心知识小结
    • MySQL 45讲
  • Redis

    • Redis核心入门知识简记
  • Spring
  • SpringCloud Alibaba
  • 开发工具

    • Git详解
    • Maven详解
    • Docker详解
    • Linux常用命令
  • 在线工具

    • json (opens new window)
    • base64编解码 (opens new window)
    • 时间戳转换 (opens new window)
    • unicode转换 (opens new window)
    • 正则表达式 (opens new window)
    • md5加密 (opens new window)
    • 二维码 (opens new window)
    • 文本比对 (opens new window)
  • 学习资源

    • 计算机经典电子书PDF
    • hot120
GitHub (opens new window)
  • IO流

    • Java基础IO总结
    • Java IO中的设计模式
    • Java IO模型
    • IO多路复用详解
  • 并发编程

    • 并发编程基础总结
    • 深入理解volatile关键字
    • 深入理解synchronized关键字
      • synchronized是什么?有什么用?
      • synchronized基础使用示例
        • synchronized修饰静态方法
        • synchronized修饰实例方法
        • synchronized修饰代码块
      • synchronized 的底层原理
        • synchronized 同步语句块的情况
        • synchronized 修饰方法的情况
      • synchronized如何保证可见性、有序性、可重入性
        • 可见性
        • 有序性
        • 可重入性
      • synchronized锁粗化和锁消除
        • 锁粗化
        • 锁消除
        • synchronized的锁升级
        • 原理详解
        • 小结
        • 代码印证
        • 补充jol-core包的其他妙用
        • synchronized和ReentrantLock的区别
  • JVM

    • JVM基础总结
  • Java进阶
  • 并发编程
BlazeMaple
2024-01-16
目录

深入理解synchronized关键字

# synchronized是什么?有什么用?

synchronized 是 Java 中的一个关键字,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

在 Java 早期版本中,synchronized 属于 重量级锁,效率低下。这是因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

在 Java 6 之后, synchronized 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,这些优化让 synchronized 锁的效率提升了很多。因此, synchronized 还是可以在实际项目中使用的,像 JDK 源码、很多开源框架都大量使用了 synchronized

关于偏向锁:由于偏向锁增加了 JVM 的复杂性,同时也并没有为所有应用都带来性能提升。因此,在 JDK15 中,偏向锁被默认关闭(仍然可以使用 -XX:+UseBiasedLocking 启用偏向锁),在 JDK18 中,偏向锁已经被彻底废弃(无法通过命令行打开)。

# synchronized基础使用示例

# synchronized修饰静态方法

给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。

public class SynchronizedDemo {
private static Logger logger = LoggerFactory.getLogger(SynchronizedDemo.class);

    private static int count = 0;

    /**
     * synchronized作用域静态类上
     */
    public synchronized static void method() {
        count++;
    }

    public static void main(String[] args) {
        IntStream.rangeClosed(1,1_0000)
            .parallel()
            .forEach(i->SynchronizedDemo.method());

        IntStream.rangeClosed(1,1_0000)
            .parallel()
            .forEach(i->new SynchronizedDemo().method());

        System.out.println("count: "+count);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//加synchronized修饰
count: 20000
//不加synchronized修饰
count: 6754    
1
2
3
4

# synchronized修饰实例方法

public class SynchronizedDemo {
    private static Logger logger = LoggerFactory.getLogger(SynchronizedDemo.class);

    private static int count = 0;

    /**
     * synchronized作用域实例方法上
     */
    public synchronized  void method() {
        count++;
    }

    public static void main(String[] args) {
        IntStream.rangeClosed(1,1_0000)
            .parallel()
            .forEach(i->new SynchronizedDemo().method());

        System.out.println("count: " + count);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
count: 7260
1

最终的结果却不是10000

因为synchronized 作用于实例方法,会导致每个线程获得的锁都是各自使用的实例对象,而++操作又非原子操作,导致互斥失败进而导致数据错误

在这里插入图片描述

所以正确的使用方式是多个线程使用同一个对象调用该方法

SynchronizedDemo demo = new SynchronizedDemo();
IntStream.rangeClosed(1,1_0000)
    .parallel()
    .forEach(i->demo.method());
1
2
3
4

静态 synchronized 方法和非静态 synchronized 方法之间的调用不互斥!如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

# synchronized修饰代码块

对括号里指定的对象/类加锁:

  • synchronized(object) 表示进入同步代码库前要获得 给定对象的锁。
  • synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁
synchronized(this) {
    //业务代码
}
1
2
3

# synchronized 的底层原理

# synchronized 同步语句块的情况

public class SynchronizedDemo {


    private static int count = 0;

    /**
     * synchronized作用域实例方法上
     */
    public void method() {
        synchronized (this) {
            count++;
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        IntStream.rangeClosed(1, 1_0000)
            .parallel()
            .forEach(i -> demo.method());

        System.out.println("count:" + count);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

先使用javac指令生成class文件

javac SynchronizedDemo.java
1

然后再使用反编译javap

javap -c -s -v  SynchronizedDemo.class
1

最终看到method方法的字节码指令,可以看到关键字synchronized 的锁是通过monitorenter和monitorexit来确保线程间的同步。

 public void method();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #7                  // Field count:I
         7: iconst_1
         8: iadd
         9: putstatic     #7                  // Field count:I
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# synchronized 修饰方法的情况

再将synchronized 关键字改到方法上再次进行编译和反编译

public synchronized void method() {
     count++;    
}
1
2
3

可以看到synchronized 实现锁的方式编程了通过ACC_SYNCHRONIZED关键字来标明该方法是一个同步方法。

 public synchronized void method();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field count:I
         3: iconst_1
         4: iadd
         5: putstatic     #7                  // Field count:I
         8: return
      LineNumberTable:
        line 14: 0
        line 15: 8
1
2
3
4
5
6
7
8
9
10
11
12
13

从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

不过两者的本质都是对对象监视器 monitor 的获取。

这些指令如何实现"锁"的?

每个线程使用的实例对象都有一个对象头,每个对象头中都有一个Mark Word,当我们使用synchronized 关键字时,这个Mark Word就会指向一个monitor。 这个monitor锁就是一种同步工具,是实现线程操作临界资源互斥的关键所在,在Java虚拟机(HotSpot)中,monitor就是通过ObjectMonitor实现的。

其代码如下,可以看到_EntryList、_WaitSet 、_owner三个关键属性。

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 记录线程获取锁的次数
    _waiters      = 0,
    _recursions   = 0;  //锁的重入次数
    _object       = NULL;
    _owner        = NULL;  // 指向持有ObjectMonitor对象的线程
    _WaitSet      = NULL;  // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

获取ObjectMonitor锁,经过下面几个步骤:

  1. 进入_EntryList。
  2. 尝试取锁,发现_owner区被其他线程持有,于是进入_WaitSet 。
  3. 其他线程用完锁,将count--变为0,释放锁,_owner被清空。
  4. 我们有机会获取_owner,尝试争抢,成功获取锁,_owner指向我们这个线程,将count++。
  5. 我们操作到一半发现CPU时间片用完了,调用wait方法,线程再次进入_WaitSet ,count--变为0,_owner被清空。
  6. 我们又有机会获取_owner,尝试争抢,成功获取锁,将count++。
  7. 这一次,我们用完临界资源,准备释放锁,count--变为0,_owner清空,其他线程继续进行monitor争抢。

在这里插入图片描述

# synchronized如何保证可见性、有序性、可重入性

# 可见性

每个线程使用synchronized获得锁操作临界资源时,首先需要获取临界资源的值,为了保证临界资源的值是最新的,JMM模型规定线程必须将本地工作内存清空,到共享内存中加载最新的进行操作。 当前线程上锁后,其他线程是无法操作这个临界资源的。 当前线程操作完临界资源之后,会立刻将值写回内存中,正是由于每个线程操作期间其他线程无法干扰,且临界资源数据实时同步,所以synchronized关键字保证了临界资源数据的可见性。

在这里插入图片描述

# 有序性

synchronized同步的代码块具备排他性,这就意味着同一个时刻只有一个线程可以获得锁,synchronized代码块的内部资源是单线程执行的。 synchronized遵守as-if-serial原则,线程修改最终结果是有序的

具体例子,某段线程得到锁Test.class之后,执行临界代码逻辑,可能会先执行变量b初始化的逻辑,在执行a变量初始化的逻辑,但是最终结果都会执行a+b的逻辑。这也就我们的说的保证最终结果的有序,而不保证执行过程中的指令有序。

synchronized (Test.class) {
    int a=1;
    int b=2;
    int c=a+b;
}
1
2
3
4
5

# 可重入性

Java允许同一个线程获取同一把锁两次,即可重入性,原因上文将synchronized相关的ObjectMonitor锁已经提到了,ObjectMonitor有一个count变量就是用于记录当前线程获取这把锁的次数。 例如我们的线程T1,两次执行synchronized 获取锁两次,count就自增两次变为2。 退出synchronized关键字对应的代码块,count就自减,变为0时就代表释放了这把锁,其他线程就可以争抢这把锁了。所以当我们的线程退出下面的两个synchronized 代码块时,其他线程就可以争抢Test.class这把锁了。

public void add2() {

    synchronized (Test.class) {
        synchronized (Test.class){
            list.add(1);
        }
    }
}
1
2
3
4
5
6
7
8

# synchronized锁粗化和锁消除

# 锁粗化

当jvm发现操作的方法连续对同一把锁进行加锁、解锁操作,就会对锁进行粗化,所有操作都在同一把锁中完成。

在这里插入图片描述

# 锁消除

虚拟机在JIT即时编译运行时,对一些代码上要求同步,但是检测到不存在共享数据的锁的进行消除。

下面这段代码涉及字符串拼接操作,所以jvm会将其优化为StringBuffer或者StringBuilder,至于选哪个,这就需要进行逃逸分析了。逃逸分析通俗来说就是判断当前操作的对象是否会逃逸出去被其他线程访问到。

例如下面的result ,是局部变量,没有发生逃逸,所以完全可以当作栈上数据来对待,是线程安全的,所以jvm进行锁消除,使用StringBuilder完成字符串拼接。

public String appendStr(String str1, String str2, String str3) {
        String result = str1 + str2 + str3;
        return result;
}
1
2
3
4

# synchronized的锁升级

# 原理详解

synchronized关键字在JDK1.6之前底层都是直接调用ObjectMonitor的enter和exit完成对操作系统级别的重量级锁mutex的使用,这使得每次上锁都需要从用户态转内核态尝试获取重量级锁的过程。 这种方式也不是不妥当,在并发度较高的场景下,取不到mutex的线程会因此直接阻塞,到等待队列_WaitSet 中等待唤醒,而不是原地自旋等待其他线程释放锁而立刻去争抢,从而避免没必要的线程原地自选等待导致的CPU开销,这也就是上文中讲到的synchronized工作原理的过程。

但是在并发度较低的场景下,可能就10个线程,竞争并不激烈可能线程等那么几毫秒就可以拿到锁了,而每个线程却还是需要不断从用户态到内核态获取重量级锁、到_WaitSet 中等待机会的过程,这种情况下,可能功能的开销还不如锁竞争的开销来得激烈。

所以JDK1.6之后,HotSpot虚拟机就对synchronized底层做了一定的优化,通俗来说根据线程竞争的激烈程度的不断增加逐步进行锁升级的策略。

假设有这样一个场景,有一个锁对象LockObj,用它作为锁,使用代码逻辑如下所示:

synchronized(LockObj){
//dosomething
}
1
2
3

此时有一个线程A,一开始没有线程竞争时,synchronized锁就是无锁状态,无需进行任何锁争抢的逻辑。此时锁对象LockObj的偏向锁****标志位为0,锁标记为01。

随着时间推移有几个线程开始竞争,竞争并不激烈的时候,就将锁升级为偏向锁,此时作为锁的对象LockObj的对象头偏向锁标记为1,锁标记为01,线程A开始尝试获取这把锁,如果获得这把锁或者发现持有这把锁的线程id就是线程A,则直接操作临界资源即可。当线程A发现偏向锁中指向的线程id不是自己时,就执行下面的逻辑:

  1. 线程A尝试CAS竞争这把锁,如果成功则将锁对象的markdown中的线程id设置为线程A的线程id,然后执行代码逻辑。
  2. 线程A尝试CAS竞争这把锁失败,则当持有锁的线程到达安全点的时候,直接将这个线程挂起,将偏向锁升级为轻量级锁,然后持有锁的线程继续自己的逻辑,线程A继续等待机会。

在这里插入图片描述

什么叫安全点?

这里可以通俗的理解一下,安全点就是代码执行到的一个特殊位置,当线程执行到这个位置时,我们可以将线程暂停下来,让我们在暂停期间做一些处理。上文中将偏向锁升级为轻量级锁就是在安全点将线程暂停一下,将锁升级为轻量级锁,然后再让线程进行进一步的工作。

关于安全点的更多介绍,可以参考这篇文章

每日一面 - 什么是 Safepoint? (opens new window)

升级为轻量级锁时,偏向锁标记为0,锁标记变为是00。此时,如果线程A需要获取这个轻量级锁,过程如下:

  1. 判断当前这把锁是否为轻量级锁,如果是则在线程栈帧中划出一块空间,存放这把锁的信息,这里就把它称为"锁记录",并将锁对象的markword复制到锁记录中。

在这里插入图片描述

  1. 复制成功之后,通过CAS的方式尝试将锁对象头中markword更新为锁记录的地址,并将owner指向锁对象头的markword。如果这几个步骤操作成功,则说明取锁成功了。

在这里插入图片描述

  1. 如果失败,jvm则会去查看锁对象中的markword是否指向线程A的锁空间,如果是线程A则代表锁重入,则线程A可以操作临界资源。如果不是线程A,则说明这把锁被别的线程持有了,线程A再次进行原地自旋等待,如果自旋超过10次(默认设置为10次)还没有得到锁则将锁升级为重量级锁。

升级为重量级锁时,锁标记为0,锁状态为10。

# 小结

经过上述的讲解我们对锁升级有了一个全流程的认识,在这里做个阶段小结:

  1. 无线程竞争,无锁状态:偏向锁标记为0,锁标记为01。
  2. 存在一定线程竞争,大部分情况下会是同一个线程获取到,升级为偏向锁,偏向标记为1,锁标记为01。
  3. 线程CAS争抢偏向锁锁失败,锁升级为轻量级锁,偏向标记为0,锁标记为00。
  4. 线程原地自旋超过10次还未取得轻量级锁,锁升级为重量级锁,避免大量线程原地自旋造成没必要的CPU开销,偏向锁标记为0,锁标记为10。

# 代码印证

上文讲解锁升级的之后,一直在说对象头的概念,为了能够直观的看到锁对象中对象头锁标记和锁状态的变化,这里引入一个jol工具。

<!--jol内存分析工具-->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
1
2
3
4
5
6

然后声明一下锁对象。

public class Lock {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
1
2
3
4
5
6
7
8
9
10
11

首先是无锁状态的代码示例,没有任何线程争抢逻辑,通过jol工具打印锁对象信息即可。

public class Lockless {
    public static void main(String[] args) {
        Lock object=new Lock();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}
1
2
3
4
5
6

打印结果如下,只需关注第一行的object header,可以看到第一列的00000001,后3位为001,偏向锁标记为0,锁标记为01,001这就是无锁状态

com.blazemaple.concurrency.Lock 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8

接下来是偏向锁,需要注意的是偏向锁必须在jvm启动后的一段时间才会运行,所以如果想打印偏向锁必须让线程休眠那么几秒

public class LockBiasaed {

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(10000);
        Lock object = new Lock();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}
1
2
3
4
5
6
7
8

输出结果如下,可以看到对象头的信息为00000101,此时锁标记为1即偏向锁标记,锁标记为01,101即偏向锁。

com.blazemaple.concurrency.Lock 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8

然后的轻量级锁的印证,我们只需使用Lock对象作为锁即可。

public class LightweightLock {
    public static void main(String[] args) {
        Lock object = new Lock();
        synchronized (object) {
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    }
}
1
2
3
4
5
6
7
8

可以看到轻量级锁锁标记为0,锁标记为00,000即轻量级。

com.blazemaple.concurrency.Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           68 f5 bf b6 (01101000 11110101 10111111 10110110) (-1228933784)
      4     4        (object header)                           27 00 00 00 (00100111 00000000 00000000 00000000) (39)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8

最后就是重量级锁了,只需打印出锁对象的哈希码即可将其升级为重量级锁。

public class HeavyweightLock {
    public static void main(String[] args) {
        Lock object = new Lock();
        synchronized (object) {
            System.out.println(object.hashCode());
        }

        synchronized (object) {
            System.out.println(ClassLayout.parseInstance(object).toPrintable());

        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出结果为10001010,偏向锁标记为0,锁标记为10,010为重量级锁。

com.blazemaple.concurrency.Lock object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           0a c2 4b 01 (00001010 11000010 01001011 00000001) (21742090)
      4     4        (object header)                           c8 01 00 00 (11001000 00000001 00000000 00000000) (456)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int Lock.count                                0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1
2
3
4
5
6
7
8

# 补充jol-core包的其他妙用

jol不仅仅可以监控Java进程的锁情况,在某些场景下,我们希望通过比较对象的地址来判断当前创建的实例是否是多例,是否存在线程安全问题。此时,我们就可以VM对象的方法获取对象地址,如下所示:

public static void main(String[] args) throws Exception {
    //打印字符串aa的地址
    System.out.println(VM.current().addressOf("aa"));
}
1
2
3
4

# synchronized和ReentrantLock的区别

可以从三个角度来了解两者的区别:

  1. 从实现角度:synchronized是JVM层面实现的锁,ReentrantLock是属于Java API层面实现的锁,所以用起来需要手动上锁lock和释放锁unlock。
  2. 从性能角度:在JDK1.6之前可能ReentrantLock性能更好,在JDK1.6之后由于JVM对synchronized增加适应性自旋锁、锁消除等策略的优化使得synchronized和ReentrantLock性能并无太大的区别。
  3. 从功能角度:ReentrantLock相比于synchronized增加了更多的高级功能,例如等待可中断、公平锁、选择性通知等功能。
帮助我们改善此页面! (opens new window)
上次更新: 2024/08/13, 09:07:12
深入理解volatile关键字
JVM基础总结

← 深入理解volatile关键字 JVM基础总结→

最近更新
01
SpringCloud Alibaba实战
08-22
02
SpringCloud Alibaba核心知识
08-22
03
两数之和
08-08
更多文章>
Theme by Vdoing | Copyright © 2023-2024 BlazeMaple
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式