tryLock()
可能无法获取锁定。因此,如果我们使用返回值执行工作,那么我们可能根本不做任何工作。
Lock lock = new ReentrantLock();
boolean isLocked = lock.tryLock();
if (isLocked) {
try {
doWork();
} finally {
lock.unlock();
}
}
synchronized
将阻塞直到获得锁为止,因此我们知道doWork()
最终将完成。那么,在获取锁之前,我们应该在循环内
tryLock()
正确吗?boolean isLocked = false;
while (!isLocked) {
isLocked = lock.tryLock();
Thread.sleep(100);
}
if (isLocked) {
try {
doWork();
} finally {
lock.unlock();
}
}
最佳答案
几乎不会,不。
如果您需要获得锁,则只需使用lock()
调用即可。如果有的话,它将立即获得它,否则,将等待它。重复循环tryLock()
也具有等待锁(如果锁不可用)的效果,但不允许操作系统控制适当的阻塞和唤醒等待线程。相反,等待线程将继续占用CPU,从而消耗CPU时间和功率,同时等待释放锁。
在最坏的情况2中,拥有锁的线程可能会被上下文切换出,并等待可用的CPU继续运行,但是该CPU不可用,因为正在忙于tryLock
上的线程正在使用它。这几乎是暂时性的死锁-临时性的,因为最终tryLock
线程将使用其量子并且被调度,但是整个过程可能比使用lock()
的通常方法花费许多数量级。
只能说tryLock()
的用途非常有限。如果您在紧密循环中使用tryLock()
,则几乎可以肯定您做错了什么。当您确实不需要获取锁定并可以执行其他工作并且性能很重要时,最好使用此方法。
例如,您必须在几个独立的对象上执行相同的操作,每个对象都有自己的锁。默认方法只是简单地依次对每个对象进行lock()
和unlock()
。一种可能更快的方法是,首先对每个对象进行tryLock()
,更新对其获得锁的对象(也许您多次执行此操作),然后将对您第一次丢失的所有对象返回lock()
。通过最初不等待任何锁定,而仅继续处理其余对象,可以在竞争的系统中获得更高的更新率。tryLock()
可能有用的另一种情况是,您可以通过多种方式实现相同的效果。想象一下,例如某种“分布式计数器” 1,其中您有一个逻辑计数器,该逻辑计数器实现为N个单独的等效计数器,每个计数器都有自己的锁。逻辑计数器的值只是N个基础计数器的总和。要增加计数器(例如),您只需要增加N个计数器中的任何一个即可。在这种情况下,尝试更新计数器的人可以对底层计数器进行tryLock()
编码,直到找到“可用”的计数器并对其进行修改。这可能比具有单个锁的单个计数器具有更好的伸缩性。
1当然,对于简单的计数器,您可能会使用原子整数,例如AtomicInteger
或更佳的LongAdder
-但对于更复杂的结构,这可能是现实的。
2如果tryLock()
线程具有比拥有锁的线程更高的优先级,并且调度策略严格地对待优先级(即,较低优先级的线程永远不会抢占较高优先级的线程),例如SCHED_FIFO
或SCHED_RR
,则会发生更糟的情况。在那种情况下,较高优先级的线程可能会在tryLock()
上无限期旋转,因为拥有它的较低优先级的线程永远不会由于可运行的较高优先级线程的存在而唤醒。这样您甚至可以陷入僵局。