【问题标题】:Why the write threads can't get the lock when ReentrantReadWriteLock is non-fair?为什么 ReentrantReadWriteLock 不公平时写线程无法获得锁?
【发布时间】:2011-11-01 04:26:41
【问题描述】:

从How to understand the “non-fair” mode of ReentrantReadWriteLock?这个问题来看,我认为无论哪个线程先获得锁,所有线程都有相同的机会。

所以我写了这段代码来测试它:

public static void main(String[] args) {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
    final ReadLock readLock = lock.readLock();
    final WriteLock writeLock = lock.writeLock();

    // hold the write lock 3s at first
    new Thread() {
        public void run() {
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " got the write lock");
            quietSleep(3);
            writeLock.unlock();
            System.out.println(Thread.currentThread().getName() + " released the write lock");

        };
    }.start();

    // a thread want to get the read lock 1s later
    new Thread() {
        public void run() {
            quietSleep(1);
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " got the read lock");
        };
    }.start();

    // 1000 threads want to get the write lock 2s later
    for (int i = 0; i < 1000; i++) {
        new Thread() {
            public void run() {
                quietSleep(2);
                writeLock.lock();
                System.out.println(Thread.currentThread().getName() + " got the write lock");
            };
        }.start();
    }
}

private static void quietSleep(int seconds) {
    try {
        Thread.sleep(seconds * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

一开始,有一个线程拿到了写锁,并保持了3s。在此期间,一个线程想要获得读锁,然后有 1000 个线程想要获得写锁。

由于 ReentrantReadWriteLock 默认使用非公平模式,我认为写线程有很大机会获得写锁。但是我运行了很多次,每次读取线程都赢了!

输出是:

Thread-0 got the write lock
Thread-0 released the write lock
Thread-1 got the read lock

我对“不公平”的理解有误吗?


更新 根据 paxdiablo 的回答,我将代码修改为:

new Thread() {
    public void run() {
        quietSleep(1);
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + " got the write lock");

    };
}.start();

for (int i = 0; i < 1000; i++) {
    new Thread() {
        public void run() {
            quietSleep(2);
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " got the read lock");
        };
    }.start();
}

现在有一个线程想要写锁,1000 个读线程想要读锁。但输出是:

Thread-0 got the write lock
Thread-0 released the write lock
Thread-1 got the write lock

看来还是“先到先得”。

【问题讨论】:

  • 仅供参考 - 在我的 Linux 机器上进行测试时,我似乎看到了这种“先到先得”的行为,但是当我们将相同的代码粘贴到 Windows 机器上时,我们确实看到了饥饿大多数时候,同一线程似乎一直在获得锁定。因此,正如 JavaDoc(和 @paxdiablo)所说,“公平”是一种相对保证,而“不公平”不是保证(可能是公平的,也可能不是)。
  • PS - 我的 Linux 机器有 4 个超线程内核(8 个并发线程),而 Windows 机器有 2 个虚拟内核(2 个并发线程)。所以这可能与它有关,因为我在 Linux 测试中可能从未有超过 8 次同时锁定尝试,但我们在 Windows 环境中肯定有超过 2 次。

标签: java reentrantreadwritelock


【解决方案1】:

不公平只是意味着它不必以排队的方式分发锁(先到先得)。它对如何分发它不做任何其他保证。事实上,如果它愿意,它可能仍然以排队的方式分发它们。

它可能更喜欢分发给读者,只是因为多个读者可以同时拥有锁,但如果作者获得它,所有读者和作者都会被阻止。

很久以前,我曾经不得不实现一个读写器互斥锁,它有多种模式,具体取决于您需要实现的目标:

  • 喜欢阅读器。
  • 喜欢作家。
  • 首选备用读取器/写入器。
  • 排队访问。

听起来第一个是您的系统可能正在做的事情(我说“可能”是因为尽管具有线程性质,但您的代码可能正在确定性地运行,即每次都相同)。


如果您想了解公平和不公平之间的区别,请尝试以下操作。

  • 让一个线程获得读锁,然后休眠一分钟。
  • 在该睡眠期间,让一个线程请求写锁定以进入睡眠一分钟。
  • 然后循环十次尝试获取和释放读锁。

在公平模式下,它应该是RWRRRRRRRRRR。那是因为除了第一个读锁之外,所有的锁都会等到获得并释放写锁(写优先,因为这很公平)。

在非公平模式下,你可能会看到RRRRRRRRRRRW,读锁可能都被允许跳到写锁前面,因为它们不会干扰第一个读锁,该死的公平:-)


当然,公平的概念可能因作者而异。允许读取在写入之前潜入但有限制的算法可能在规则范围内。例如,一旦请求了写锁,在排在写锁后面之前,只允许再有五个读锁偷偷通过。

我并不是说有人曾经实施过,但这肯定是一个可行的选择,可以平衡效率与公平。

【讨论】:

  • 是否有代码示例来说明“非公平”和“公平”模式的区别?
  • @Freewind,本身没有代码,但我添加了一个测试用例,希望能说明它。
  • 我按照你的建议编码,但不管公平与否,它总是RWRRRRRRRRR。见code in gist,可以更改变量fair查看结果。
  • 见我第一段的最后一句话。公平意味着它已排队,不公平意味着它没有必要排队 - 这并不意味着它必须被取消排队。
猜你喜欢
  • 2012-12-05
  • 1970-01-01
  • 1970-01-01
  • 2019-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-05
  • 2014-08-17
相关资源
最近更新 更多