在优化一些锁定内容的同时,我使用了JMH基准测试,以了解锁定一个锁定的ReentrantLock比只锁定一次要花费多少成本.当我看到jdk11比jdk21表现得更好时,我很惊讶……如果能理解为什么以及我的基准测试是否正确,那将是一件非常好的事情.

我还添加了带有同步块的Benchmark,并且根本没有任何锁定.正如预期的那样,同步块得到了优化,其性能几乎与无锁块一样,并且不同的JDK版本之间没有降级.

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class LockNoLockBenchmark {
  int counter;

  ReentrantLock lock = new ReentrantLock();


  @Benchmark
  public void noLock() {
    ++counter;
  }

  @Benchmark
  public void syncLock() {
    synchronized (new Object()) {
      ++counter;
    }
  }

  @Benchmark
  public void lockUnlock() {
    lock.lock();
    try {
      ++counter;
    } finally {
      lock.unlock();
    }
  }

  @Benchmark
  public void lockLockUnlockUnlock() {
    lock.lock();
    try {
      lock.lock();
      try {
        ++counter;
      } finally {
        lock.unlock();
      }
    } finally {
      lock.unlock();
    }
  }
}

在Intel Rocket Lake(酷睿i9)第12代英特尔(R)酷睿(TM)i9-12950HX 12核64 GB RAM上运行

  1. JDK 21
openjdk 21.0.2 2024-01-16
OpenJDK Runtime Environment (build 21.0.2+13-58)
OpenJDK 64-Bit Server VM (build 21.0.2+13-58, mixed mode, sharing)

Benchmark                                 Mode  Cnt   Score   Error  Units
LockNoLockBenchmark.lockLockUnlockUnlock  avgt   10  27.457 ± 0.876  ns/op
LockNoLockBenchmark.lockUnlock            avgt   10  11.409 ± 0.256  ns/op
LockNoLockBenchmark.noLock                avgt   10   0.280 ± 0.010  ns/op
LockNoLockBenchmark.syncLock              avgt   10   0.280 ± 0.008  ns/op
  1. JDK 11
openjdk 11.0.21 2023-10-17
OpenJDK Runtime Environment (build 11.0.21+9-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.21+9-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)

Benchmark                                 Mode  Cnt   Score   Error  Units
LockNoLockBenchmark.lockLockUnlockUnlock  avgt   10  22.414 ± 1.366  ns/op
LockNoLockBenchmark.lockUnlock            avgt   10  11.690 ± 0.407  ns/op
LockNoLockBenchmark.noLock                avgt   10   0.283 ± 0.021  ns/op
LockNoLockBenchmark.syncLock              avgt   10   0.289 ± 0.012  ns/op

我希望在使用JDK 21的情况下,性能不会下降. 我还感兴趣的是,当我需要获取一个锁定的锁时,有哪些方法可以优化代码. 谢谢

推荐答案

在JDK 14中,在JDK-8229442的上下文中对java.util.concurrent个内部代码进行了大规模重写.目标是提高并发原语的整体性能,并为虚拟线程的实现做好准备.

然而,正如经常发生的那样,一个场景的改进伴随着另一个场景的倒退.

在JDK 11中,递归锁定的the code如下所示.它有一条快速路径来判断锁是否属于当前线程.请注意,此路径上没有原子compareAndSet操作.

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

在JDK 21中,the code看起来有点不同.无论锁是否是递归的,initialTryLock总是执行compareAndSetState,这就是性能差异所在.

final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    if (compareAndSetState(0, 1)) { // first attempt is unguarded
        setExclusiveOwnerThread(current);
        return true;
    } else if (getExclusiveOwnerThread() == current) {
        int c = getState() + 1;
        if (c < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(c);
        return true;
    } else
        return false;
}

前面提到的重构already caused早先出现了性能回归,后来得到了修复.如果您的问题是由生产中的实际问题引起的,欢迎您提交错误报告.

另外需要注意的是,您的syncLock()基准测试实际上并不测量synchronized的性能,因为锁定本地非转义对象是无操作的,而且JIT编译器很高兴完全消除了不必要的锁定.

Java相关问答推荐

Annotation @ Memphier无法正常工作,并表示:class需要一个bean,但找到了2个bean:

gitlab ci不会运行我的脚本,因为它需要数据库连接'

OpenJDK、4K显示和文本质量

无法找到符号错误—Java—封装

Java函数式编程中的双值单值映射

如何才能使我的程序不会要求两次输入?

%This内置函数示例

为什么StandardOpenOption.CREATE不能通过Ubuntu在中小企业上运行?

如何使用Criteria Builder处理一对多关系中的空值?

带有可选部分的Java DateTimeForMatter

无法使用Java PreparedStatement在SQLite中的日期之间获取结果

记录是类的语法糖吗?

在整数列表中查找和可被第三个整数整除的对时出现无法解释的RunTimeError

如何制作回文程序?

活泼的一次判断成语,结果中等

如何以事务方式向ibmmq发送消息

[Guice/MissingImplementation]:未绑定任何实现

如何在 Android Studio 中删除 ImageView 和屏幕/父级边缘之间的额外空间?

元音变音字符:如何在 Java 中将Á<0x9c>转换为Ü?

为什么 Random() 的行为不符合预期?