与构造不同,ReentrantLock是非结构化的synchronized
——也就是说,您不需要使用块结构进行锁定,甚至可以跨方法持有锁定。一个例子:
private ReentrantLock lock;
public void foo() {
...
lock.lock();
...
}
public void bar() {
...
lock.unlock();
...
}
这种流不可能通过构造中的单个监视器来表示synchronized
。
除此之外,还ReentrantLock
支持锁轮询和支持超时的可中断锁等待。ReentrantLock
还支持可配置的公平策略,允许更灵活的线程调度。
此类的构造函数接受一个可选的公平参数。设置时true
,在争用情况下,锁有利于授予对等待时间最长的线程的访问权限。否则,此锁不保证任何特定的访问顺序。使用由许多线程访问的公平锁的程序可能会显示出比使用默认设置的程序更低的整体吞吐量(即,更慢;通常要慢得多),但在获取锁和保证不会出现饥饿的情况下具有较小的时间差异。但是请注意,锁的公平性并不能保证线程调度的公平性。因此,使用公平锁的许多线程之一可能会连续多次获得它,而其他活动线程没有进展并且当前没有持有锁。另请注意,不定时tryLock
方法不遵守公平设置。如果锁可用,即使其他线程正在等待,它也会成功。
ReentrantLock
也可能更具可扩展性,在更高的争用下表现得更好。您可以在此处阅读有关此内容的更多信息。
然而,这一说法受到了质疑;请参阅以下评论:
在重入锁测试中,每次都会创建一个新锁,因此没有排他锁,结果数据无效。此外,IBM 链接没有提供底层基准测试的源代码,因此无法确定测试是否正确进行。
什么时候应该使用ReentrantLock
s?根据那篇 developerWorks 文章...
答案很简单——当你真正需要它提供的东西时使用它synchronized
,比如定时锁等待、可中断锁等待、非块结构锁、多个条件变量或锁轮询。ReentrantLock
也有可扩展性的好处,如果你确实有一个表现出高争用的情况,你应该使用它,但请记住,绝大多数synchronized
块几乎没有表现出任何争用,更不用说高争用了。我建议使用同步进行开发,直到同步被证明是不够的,而不是简单地假设“性能会更好”,如果你使用ReentrantLock
. 请记住,这些是高级用户的高级工具。(而且真正高级的用户往往更喜欢他们能找到的最简单的工具,直到他们确信这些简单的工具是不够的。)和往常一样,先把它做好,然后再考虑是否必须让它更快。
在不久的将来会变得更加相关的最后一个方面与Java 15 和 Project Loom有关。ReentrantLock
在虚拟线程的(新)世界中,底层调度程序将能够比它能够更好地工作synchronized
,至少在最初的 Java 15 版本中是这样,但以后可能会进行优化。
在当前的 Loom 实现中,可以在两种情况下固定虚拟线程:当堆栈上有一个本地框架时——当 Java 代码调用本地代码 (JNI),然后再回调到 Java 时——以及在synchronized
块或方法内部时. 在这些情况下,阻塞虚拟线程将阻塞承载它的物理线程。一旦本机调用完成或释放监视器(synchronized
退出块/方法),线程将被取消固定。
如果您有一个由 a 保护的常见 I/O 操作synchronized
,请将监视器替换为 aReentrantLock
以让您的应用程序充分受益于 Loom 的可扩展性提升,甚至在我们修复监视器固定之前(或者,StampedLock
如果可以的话,最好使用更高性能) .