在 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) 堆栈帧,这可能不是很好。有没有更好的办法?我真正想要的是一种无需同步关键字即可获取和释放内在锁的方法。如果您有一种非阻塞方式来尝试获取内在锁,则可以加分。注意:我不是在问这是否是一个好主意(不是),只是在可能的情况下。现实世界的答案显然只是使用显式锁,并对尝试一次获取 N 个对象的锁的智慧进行一些反省。
最佳答案
这在普通 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 代码”,甚至比讨论的手工字节码的可移植性更差。
关于java - 是否可以显式使用 Java 内在锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63405626/