ReentrantLock 保证了当前只有一个线程可以执行 临界区 代码:

  1. public class Counter {
  2. private final Lock lock = new ReentrantLock();
  3. private int[] counts = new int[10];
  4. public void inc(int index) {
  5. lock.lock();
  6. try {
  7. counts[index] += 1;
  8. } finally {
  9. lock.unlock();
  10. }
  11. }
  12. public int[] get() {
  13. lock.lock();
  14. try {
  15. return Arrays.copyOf(counts, counts.length);
  16. } finally {
  17. lock.unlock();
  18. }
  19. }
  20. }

有些时候,这种保护有点过头。因为我们发现,任何时刻,只允许一个线程修改,也就是调用 inc() 方法是必须获取锁,但是,get() 方法只读取数据,不修改数据,它实际上允许多个线程同时调用。
总计一下:允许多个线程同时读,但只要有一个线程在写,其他线程就必须等待:

|

| 读 | 写 | | —- | —- | —- |

| 读 | 允许 | 不允许 |

| 写 | 不允许 | 不允许 |

使用 JDK 中的 ReadWriteLock 可以解决这个问题,它保证:

  • 只允许一个线程写入(其他线程既不能写入也不能读取);
  • 没有写入时,多个线程允许同时读(提高性能)。

例如,这里将上述例子使用 ReadWriteLock 编写:

  1. public class Counter {
  2. private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); // 创建实例
  3. private final Lock rlock = rwlock.readLock(); // 获取读锁
  4. private final Lock wlock = rwlock.writeLock(); // 获取写锁
  5. private int[] counts = new int[10];
  6. public void inc(int index) {
  7. wlock.lock(); // 加写锁
  8. try {
  9. counts[index] += 1;
  10. } finally {
  11. wlock.unlock(); // 释放写锁
  12. }
  13. }
  14. public int[] get() {
  15. rlock.lock(); // 加读锁
  16. try {
  17. return Arrays.copyOf(counts, counts.length);
  18. } finally {
  19. rlock.unlock(); // 释放读锁
  20. }
  21. }
  22. }

把读写操作分别用读锁和写锁来加锁,在读取时,多个线程可以同时获得读锁,这样就大大提高了并发读的执行效率。
使用 ReadWriteLock 时,适用条件是同一个数据,有大量线程读取,但仅有少数线程修改。
例如,一个论坛的帖子,回复可以看做写入操作,它是不频繁的,但是,浏览可以看做读取操作,是非常频繁的,这种情况就可以使用 ReadWriteLock

小结

使用 ReadWriteLock 可以提高读取效率:

  • ReadWriteLock 只允许一个线程写入;
  • ReadWriteLock 允许多个线程在没有写入时同时读取;
  • ReadWriteLock 适合读多写少的场景。