写点什么

JVM 锁优化:Java 原生锁的背后!

作者:java易二三
  • 2023-08-30
    湖南
  • 本文字数:2596 字

    阅读完需:约 9 分钟

1. 前言

在多线程编程中,锁是一个非常重要的话题。Java 原生提供了 synchronized 关键字和 Lock 接口等方式来实现锁。而 JVM 在处理锁时,也做了不少优化。本文将阐述 JVM 对 Java 的原生锁做了哪些优化。

2. 摘要

本文首先简单介绍了 Java 中锁的两种实现方式:synchronized 和 Lock 接口。然后分别从 JVM 级别进行解析:synchronized 的优化主要包括:偏向锁、轻量级锁和重量级锁等;而 Lock 接口的优化主要包括:公平锁和非公平锁等。本文最后给出了相应的代码示例和测试用例,最终得出结论:JVM 对 Java 的原生锁做了很多优化,这些优化大大提升了锁的性能和效率。

3. 正文

3.1 Java 中锁的实现方式

在 Java 中,锁主要有两种实现方式:synchronized 和 Lock 接口。

3.1.1 synchronized

synchronized 关键字是 Java 中最基本的锁实现方式。它是在 JVM 层面实现的,具有自动释放锁、重入锁和可见性等特性,使用简单方便。

3.1.2 Lock 接口

Lock 是 JDK 提供的高级锁机制,它的实现方式是基于 Java 中的 AQS(AbstractQueuedSynchronizer)类实现的。AQS 是一个同步框架,它可以用于构建同步器,其中包括 ReentrantLock 和 ReentrantReadWriteLock 等。

3.2 synchronized 的优化

JVM 对 synchronized 锁进行了很多优化,主要包括以下三种:

3.2.1 偏向锁

偏向锁:这种优化是 JVM 为了减少同步无竞争情况下的性能消耗而引入的。当一个线程访问同步块并获取锁时,JVM 会在对象头和线程栈上做一个记录,记录下线程 ID、对象头指针和锁标志。当这个线程持有锁的时间超过一定阈值时,JVM 会将锁升级为轻量级锁;当线程竞争锁时,锁会直接升级为重量级锁。

3.2.2 轻量级锁

轻量级锁:这种优化是为了在低竞争条件下,减少锁的开销而引入的。当一个线程请求锁时,JVM 会优先使用 CAS(Compare And Swap)操作将对象头指针指向线程栈上的锁记录,如果 CAS 操作成功,则表示该线程获取到了锁,并且锁的状态改变为轻量级锁状态;如果 CAS 操作失败,则表示有其他线程竞争此锁,线程会进入自旋,不需要系统调用,因此效率极高。

3.2.3 重量级锁

重量级锁:这种优化是为了在高竞争条件下,保证线程竞争公平而引入的。如果一个线程尝试获取锁时,发现锁已经被其他线程持有,它会陷入阻塞状态,直到锁被释放。

3.3 Lock 接口的优化

Lock 接口相比于 synchronized 关键字,优化点更多,主要包括以下两种:

3.3.1 公平锁

公平锁:在多个线程竞争同一个锁时,如果锁是公平锁,则每个线程都会按照它们发出请求的时间顺序来获取锁,这种情况下线程获取锁的顺序是有序的。

3.3.2 非公平锁

非公平锁:在多个线程竞争同一个锁时,如果锁是非公平锁,则不保证线程获取锁的顺序,这样可能会导致某些线程一直无法获取到锁。

3.4 代码示例和测试用例

下面给出了对于 synchronized 和 Lock 接口的相应代码示例和测试用例:

3.4.1 synchronized 实现的锁机制

示例代码如下:

java复制代码public class SyncJava {
//初始为0 private int count = 0;
public synchronized void increment() { count++; }
public synchronized void decrement() { count--; }
public int getCount() { return count; }
public static void main(String[] args) throws InterruptedException { SyncJava sync = new SyncJava(); ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) { executor.execute(() -> { sync.increment(); sync.decrement(); }); }
//关闭线程池 executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println(sync.getCount()); }}

复制代码

测试用例说明:创建一个 SyncJava 实例,实现了 increment()方法和 decrement()方法,其中使用了 synchronized 关键字保证线程安全。创建一个线程池,启动 1000 个线程,每个线程执行一次 increment()方法和一次 decrement()方法。最终输出 count 的值,究竟该 count 值会是多少呢?咱们拭目以待。

实际运行截图如下:


由于 synchronized 关键字保证了线程安全,最终 count 值只会跟初始值一样,为 0,实际上输出结果也跟预期结果是一致的。

3.4.2 Lock 接口实现的锁机制

示例代码如下:

java复制代码public class LockTest {
private Lock lock = new ReentrantLock(); private int count = 0;
public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } }
public void decrement() { lock.lock(); try { count--; } finally { lock.unlock(); } }
public int getCount() { return count; }
public static void main(String[] args) throws InterruptedException { LockTest demo = new LockTest(); ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) { executor.execute(() -> { demo.increment(); demo.decrement(); }); }
executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println(demo.getCount()); }}
复制代码

测试用例说明:创建一个 LockTest 实例,实现了 increment()方法和 decrement()方法,其中使用了 Lock 接口来实现线程安全。与上述测试保持一致,也是创建一个线程池,启动 1000 个线程,每个线程执行一次 increment()方法和一次 decrement()方法,最终测试输出 count 的值。

实际运行截图如下:

很明显,结果为 0,这就验证了多线程中安全执行 increment()和 decrement()方法,每个线程都会按照它们发出请求的时间顺序来获取锁,哪怕执行一千次一万次,count 值也是顺序一加一减,最后 count 值只能为 0.

4. 结论

总的来说,JVM 对 Java 的原生锁进行了很多优化,主要包括偏向锁、轻量级锁、重量级锁、公平锁和非公平锁等。这些优化大大提升了锁的性能和效率。在实际编程中,我们需要了解 Java 中不同的锁实现方式,并根据实际场景选择最适合的锁。

最后

大家如果觉得看了本文有帮助的话,麻烦给个三连(点赞、分享、转发)支持一下哈。

用户头像

java易二三

关注

还未添加个人签名 2021-11-23 加入

还未添加个人简介

评论

发布
暂无评论
JVM锁优化:Java原生锁的背后!_Java_java易二三_InfoQ写作社区