首页 > 解决方案 > 为什么书中的 ReentrantLock 算法不起作用?

问题描述

今天我读了Nir ​​Shavit, Maurice Herlihy, The Art of Multiprocessor Programming并且遇到了一件(对我个人而言)非常难以理解的事情。

所以,我在 java 中找到了ReentrantLock的实现(对我来说是第 188 页,第 8 章):

class SimpleReentrantLock implements Lock {

    Lock lock;
    Condition condition;
    long owner, holdCount;

    SimpleReentrantLock() {
        lock = new SimpleLock();
        condition = lock.newCondition();
        owner = 0;
        holdCount = 0;
    }

    @Override
    public void lock() {
        long me = Thread.currentThread().getId();
        lock.lock();
        if (owner == me) {
            holdCount++;
            return;
        }

        while (holdCount != 0) {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        owner = me;
        holdCount = 1L;
    }

    @Override
    public void unlock() {
        lock.lock();
        try {
            if (holdCount == 0 || owner != Thread.currentThread().getId()) {
                throw new IllegalMonitorStateException();
            }

            holdCount--;
            if (holdCount == 0) {
                condition.signal();
            }
        } finally {
            lock.unlock();
        }
    }
    // Other methods for Lock interface...
}

我分析了这段代码,但仍然没有完全理解它。

所以,我可以这样使用ReentrantLockfrom java.util.concurrent.locks

lock.lock();
lock.lock();
// Some code here...
lock.unlock();
lock.unlock();

没关系,因为它是 ReentrantLock,我可以多次获取临界区。

例如,您可以从本书中找到自旋锁实现:

class TASLock implements Lock {
    private AtomicBoolean state = new AtomicBoolean(false);

    @Override
    public void lock() {
        while(state.getAndSet(true));
    }

    @Override
    public void unlock() {
        state.set(false);
    }

    // Other Lock methods...
}

此实现按预期工作。

SimpleReentrantLock因此,您可以从下一件事中注意到:

 lock = new SimpleLock();

正如作者告诉我们的:

我们将内部锁字段初始化为(虚构的)SimpleLock 类的对象,该类可能是不可重入的

但实际上,我已经实现了不可重入锁(TASLock),所以我将进行下一个内联:

lock = new TTASLock();

最后,当我尝试执行下一个代码时,我会陷入僵局:

new Thread(() -> {
            lock.lock();
            lock.lock();
            System.out.println("No deadlock found.");
            lock.unlock();
            lock.unlock();
}).start();

它看起来很清楚,因为在lock方法中我们有这样的代码:

 lock.lock();

我们实际上试图在没有任何先决条件的情况下在同一个锁对象上两次获取临界区。

书中是否指出了错误的算法?还是我没明白什么?

标签: javamultithreadingconcurrency

解决方案


lock() 方法中缺少 lock.unlock()。该算法是正确的,这是一个简单的疏忽。
如书中所述:

因为这两个字段是原子操作的,所以我们需要一个内部短期锁。

要回答评论中的问题,这里是 lock() 的更正版本:

    public void lock() {
    long me = Thread.currentThread().getId();
    lock.lock();
    try{
    if (owner == me) {
        holdCount++;
        return;
    }

    while (holdCount != 0) {
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    owner = me;
    holdCount = 1L;
    } finally {        
        lock.unlock();  // this call is missing
    }
}

推荐阅读