【问题标题】:Is it possible to use Java intrinsic locks explicitly?是否可以显式使用 Java 内部锁?
【发布时间】:2020-12-03 22:05:00
【问题描述】:

在 Java 中,假设你有一个数组:

Object[] objs = {o1, o2, ... , oN};

还有一个关键部分:

{
 critical();
}

并且您希望在持有 objs 中每个元素的内在锁的同时执行临界区。

我可以想到一种方法来做到这一点,它涉及对递归的可恶滥用:

void syncArray(int i) {
  if (i >= 0) {
    synchronized(objs[i]) {
       syncArray(i - 1);
    }
  } else {
    critical();
  }
}

syncArray(objs.length - 1);

除了丑陋之外,这还需要 O(N) 堆栈帧,这可能不是很好。有没有更好的办法?我真正想要的是一种在没有 synchronized 关键字的情况下获取和释放内在锁的方法。如果您以非阻塞方式尝试获取内部锁,则可加分。

注意:我不是在问这是否是一个好主意(不是),只是如果它可能的话。现实世界的答案显然只是使用显式锁,并且对尝试一次为 N 个对象获取锁的智慧进行一些反思。

【问题讨论】:

  • i>=4 时,假设您可以“展开” 2 或 4 并使用 4 个嵌套的 synchronized(objs[i-0..3]) 块获取 4 个锁。如果递归不只是变成循环,那可能会 JIT 变成不那么糟糕的 asm。
  • 您不可以使用objs 作为锁吗?
  • 所以稍微回答一下我自己的问题:我必须查找是否可以将 monitorenter 和 monitorexit 字节码指令包装在它们自己的函数中,这样它们就可以在不配对的情况下被调用。事实证明,这段代码可能会被 JVM 实现丢弃,因为规范说它是“允许的,但不是必需的”强制执行函数退出时对象的 monitorenter 和 monitorexit 调用的数量必须相同。这意味着我在这个问题中所要求的是不可能的! docs.oracle.com/javase/specs/jvms/se8/html/…
  • 虽然这听起来好像你不能将这些指令放在它们自己的函数中,但你能构造一个在循环中发出它们的函数吗?
  • 所以您正在考虑是否可以直接使用 Java 字节码,无论您是否可以编写将编译为此类字节码的 Java 源代码? (或通过java-bytecode-asm 库来创建它)。

标签: java locking synchronized monitor locks


【解决方案1】:

这在普通 Java 代码中是不可能的¹,但可以在字节码级别完成。

但是,这样做并不值得,因为它不能解决递归方法的问题,堆栈消耗,这似乎是你唯一关心的问题。

这已在Does synchronized block have max reentrant limit? 中进行了详细说明。this answer 中的示例程序演示了在循环中获取对象监视器的字节码展示了可用堆栈空间与您可以进行的监视器获取的最大数量之间的直接关系。

换句话说,即使是在循环中获取对象监视器的手工字节码也会消耗 O(n) 堆栈,并且可能会像递归方法一样失败。

链接答案的示例代码在循环中获取同一对象的监视器,但即使没有得到优化,也没有理由假设获取不同对象的监视器可以使用更少的堆栈空间。

关于您的“额外问题”,非阻塞“try-monitorenter”没有字节码操作。 sun.misc.Unsafe 的某些版本有 tryMonitorEnter 方法,但这超出了任何标准。

关于 结构化锁定 要求you mentioned in a comment,这没有问题。它只要求在方法退出时,该方法不能持有它在进入时尚未持有的任何内在锁。因此,如果您的方法在循环中获取所有监视器,执行关键部分,并在循环中释放相同的监视器,那么它在形式上是正确的。那是假设数组在中间永远不会被修改。

但如上所述,编写此类字节码几乎没有什么收获,因为 JVM 不会在运行时优化此类不常见的代码,并且每次获取仍会消耗堆栈空间。


¹ 好吧,上面提到了sun.misc.Unsafe,但使用它的代码不算“普通 Java 代码”,甚至比讨论的手工字节码更不便携。

【讨论】:

    最近更新 更多