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