【问题标题】:Why in ReentrantReadWriteLock, should the readLock() be unlocked before writeLock().lock()?为什么在 ReentrantReadWriteLock 中,readLock() 应该在 writeLock().lock() 之前解锁?
【发布时间】:2013-05-29 21:26:12
【问题描述】:

来自ReentrantReadWriteLock class javadoc

void processCachedData() {
  rwl.readLock().lock();
  if (!cacheValid) {
     // Must release read lock before acquiring write lock
5:   rwl.readLock().unlock();
6:   rwl.writeLock().lock();
     // Recheck state because another thread might have acquired
     //   write lock and changed state before we did.
     if (!cacheValid) {
       data = ...
       cacheValid = true;
     }
     // Downgrade by acquiring read lock before releasing write lock
14:  rwl.readLock().lock();
15:  rwl.writeLock().unlock(); // Unlock write, still hold read
  }

  use(data);
  rwl.readLock().unlock();
}

为什么我们必须在获得评论中写的写锁之前释放读锁?如果当前线程持有读锁,那么无论当前线程是否也持有读锁,都应该允许在其他线程不再读取时获取写锁。这是我所期望的行为。

我希望第 5 行和第 6 行的锁升级和第 14 行和第 15 行的锁降级在 ReentrantReadWriteLock 类内部完成。为什么这不可能?

换句话说,我希望代码可以像这样正常工作:

void processCachedData() {
  rwl.readLock().lock();
  if (!cacheValid) {
     // The readlock is upgraded to writeLock when other threads 
     // release their readlocks.
     rwl.writeLock().lock();
     // no need to recheck: other threads can't have acquired  
     // the write lock since this thread still holds also the readLock!!!
     if (!cacheValid) {  
       data = ...
       cacheValid = true;
     }
     // Downgrade by acquiring read lock before releasing write lock
     rwl.writeLock().unlock(); // Unlock write, still hold read
  }

  use(data);
  rwl.readLock().unlock();
}

这看起来是一种更好、更安全的处理锁定方式,不是吗?

有人可以解释这种奇怪实现的原因吗?谢谢。

【问题讨论】:

  • 你需要考虑如果两个或多个线程同时尝试这样做会发生什么。

标签: java multithreading concurrency locking java.util.concurrent


【解决方案1】:

将读锁升级为写锁的问题是,如果两个线程同时尝试这样做,可能会导致死锁。考虑以下顺序:

  1. 线程 A:获取读锁
  2. 线程 B:获取读锁(请注意,两个线程现在共享读锁)。
  3. 线程 A:尝试获取写锁(线程 B 持有读锁时阻塞)
  4. 线程 B:尝试获取写锁(线程 A 持有读锁时阻塞)

现在出现了死锁。线程 A 或 B 都不能继续,也永远不会释放读锁。

【讨论】:

  • 同步死锁回复(双关语/矛盾修饰意:-D)
  • 一些读取器/写入器锁允许线程获取“可升级读取器”锁。如果没有其他线程持有这样的锁,那么获取锁的尝试将会成功,就像获取读锁的尝试一样。但是,如果另一个线程已经持有这样的锁,那么第二次获取锁的尝试将阻塞,直到第一个线程释放其锁或将其“降级”为不可升级。这种锁的优点是保证在升级到只写锁之前对锁定资源所做的任何观察都将保持有效。
【解决方案2】:

Javadoc 明确指出不可能将读锁升级为写锁。 原因是因为它会造成死锁。

  • 线程 1 获取读锁
  • 线程 2 获取读锁
  • 线程1要求升级锁写
  • 线程2要求升级锁写

两个线程现在都在互相等待……永远。

【讨论】:

    【解决方案3】:

    可重入允许从写锁降级为读锁,方法是获取写锁,然后是读锁,然后释放写锁。但是,无法从读锁升级到写锁(这会导致死锁)。

    来源:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html

    【讨论】:

    • 这也写在文档中。我提出的问题是 why ?如果是我实现这个东西,我会假设持有读锁不会阻止获取写入。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多