可重入锁与可重入读写锁

10
我在想,ReentrantLockReentrantReadWriteLock在逻辑上有什么区别。换句话说,是否存在只需要锁定而不需要读/写锁或反之的情况,即读/写锁和锁不足够的情况?
考虑以下主要示例,它只使用了一个锁:
public class Counter {

  private int counter = 0;
  private ReentrantLock lock = new ReentrantLock(true);

  public void increment() {
    lock.lock();
    counter += 1;
    lock.unlock();
  }

  public int getCounter() {
    return counter;
  }
}

考虑并与之前的示例进行比较,以下是读/写锁示例:

public class Counter {

  private int counter = 0;
  private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

  public void increment() {
    lock.writeLock().lock();
    try {
      counter += 1;
    } finally {
      lock.writeLock().unlock();
    }
  }

  public int getCounter() {
    lock.readLock().lock();
    try {
      return counter;
    } finally {
      lock.readLock().unlock();
    }
  }
}

哪一种实现方式在线程安全方面是正确的?为什么?

使用ReadWriteLock,读锁是共享的,而写锁是独占的。换句话说,多个线程可以同时读取,只要没有线程在写入并且一个或多个线程正在读取时,没有线程能够写入。当您需要此功能时,请使用ReadWriteLock。另一方面,Lock是互斥的。 - Slaw
请注意,您的第一个示例中使用的 Lock 不是线程安全的,因为对 counter 的读取没有受到 Lock 的保护。 - Slaw
2个回答

3

使用 ReentrantLock 或者 ReentrantReadWriteLock 取决于你需要解决的具体问题。

在第一个示例中,getCounter() 方法没有锁。因此,在增量方法执行时持有 ReentrantLock 锁的线程仍然可以获取计数器。

在第二个示例中,在增量方法执行时持有 writeLock() 锁的线程将不允许获取计数器。而持有 readLock() 锁的线程则会防止计数器增加。 多个线程可以同时持有 readLock() 锁并读取你的 counter 参数。


关于“获取计数器是可能的”这一问题,Java保证未同步的getCounter()方法将返回counter变量的初始值或者存储在其中的某个值。但是,如果没有同步,就无法保证该值的最新状态。在某些平台上,尽管可能性很小,未同步的getCounter()调用可能会在另一个线程调用了数千次increment()方法后,在某个线程中返回初始值。 - Solomon Slow

0

ReentrantReadWriteLock 可以通过仔细控制 AbstractQueuedSynchronizer$state 来提供更高的性能,特别是在读操作比写操作多的情况下。

如果没有仔细控制,那么控制的开销将比 ReentrantLock 更高。

对于您的第一个示例,确实不需要考虑 getCounter() 方法的原子性问题,但应该考虑可见性问题。即使其他线程已经增加了计数器,某个线程可能仍然看到初始值。


你能解释一下可见性问题吗? - joker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接