tryLock() 循环?

jfo*_*x78 3 java concurrency multithreading

tryLock()可能无法获取锁。因此,如果我们使用返回值来执行工作,我们可能根本无法完成工作。

Lock lock = new ReentrantLock();
boolean isLocked = lock.tryLock();

if (isLocked) {
    try {
        doWork();
    } finally {
        lock.unlock();
    }
}
Run Code Online (Sandbox Code Playgroud)

Asynchronized会阻塞直到获得锁,所以我们知道doWork()最终会完成。

那么,我们应该tryLock()在循环内直到获得锁是正确的吗?

boolean isLocked = false;

while (!isLocked) {
    isLocked = lock.tryLock();
    Thread.sleep(100);
}

if (isLocked) {
    try {
        doWork();
    } finally {
        lock.unlock();
    }
}
Run Code Online (Sandbox Code Playgroud)

Bee*_*ope 5

几乎从来没有,没有。

如果您需要获取锁,您只需使用lock()调用即可。如果可用,这将立即获取它,否则等待。如果锁不可用,重复循环tryLock()也会产生等待锁的效果,但不允许适当的操作系统控制阻塞和唤醒等待线程。相反,等待线程将继续占用 CPU,消耗 CPU 时间和电量,同时等待锁被释放。

在最坏的情况2 中,拥有锁的线程可能会被上下文切换出来并等待可用的 CPU 继续运行,但 CPU 不可用,因为它正被一个忙于循环的线程使用tryLock。这几乎是临时死锁的形式 - 临时的,因为最终tryLock线程将使用其量程并被安排关闭,但是整个过程可能比通常使用lock().

所有这一切的tryLock()用途非常有限。如果你tryLock()在一个紧密的循环中使用,你几乎肯定做错了什么。当您真的不需要获取锁并有其他可以做的工作时,最好使用它,并且性能很重要。

例如,如果您必须对多个独立的对象执行相同的操作,每个对象都有自己的锁。默认方法只是依次指向lock()unlock()每个对象。一种可能更快的方法是首先tryLock()每个对象,更新您获得锁定的任何对象(也许您这样做不止一次),然后返回到lock()您第一次错过的任何对象。通过最初不等待任何锁定,而只是继续处理其余的对象,您可能会在竞争系统中获得更高的更新率。

另一种tryLock()可能有用的情况是,您可以通过多种方式实现相同的效果。想象一下,例如某种“分布式计数器” 1,您有一个逻辑计数器,它被实现为 N 个单独的等效计数器,每个计数器都有自己的锁。的的逻辑计数器的只是N个底层计数器的总和。要增加计数器(例如),您只需要增加 N 个计数器中的任何一个。在这种情况下,尝试更新计数器的人只能tryLock()使用底层计数器,直到他找到“可用”的计数器并对其进行修改。这可能比具有单个锁的单个计数器更好地扩展。


1当然,对于一个简单的计数器,您可能会使用原子整数,例如AtomicIntegeror,更好,LongAdder但对于更复杂的结构,这可能是现实的。

2如果tryLock()线程具有比拥有锁的线程更高的优先级,并且调度策略严格处理优先级(即,较低优先级的线程从不抢占较高优先级的线程),则会出现更糟糕的情况,例如SCHED_FIFOSCHED_RR。在这种情况下,高优先级的线程可旋转无限期tryLock(),因为该公司拥有的低优先级的线程永远不会醒来由于一个可运行的高优先级线程的存在。所以你甚至可以死锁。