引言

在前几篇文章中了解了ReentrantLock、Semaphore与CountDownLatch后,J.U.C包中基于AQS实现的并发工具类还剩一个比较重要的:读写锁ReentrantReadWriteLock。读写锁在Java面试过程中是一个经常性考的题目,他涉及到的知识点比较多,导致很多人不能透彻的理解他。举几个面试中常见的问题:

  • ReentrantReadWriteLock和ReentrantLock的区别?
  • 你能用ReentrantReadWriteLock实现一个简单的缓存管理吗?
  • 你能自己实现一个简单的读写锁吗?
  • ReentrantReadWriteLock会发生写饥饿的情况吗?如果发生,有没有比较好的解决办法?

上面的问题大家都能回答出来吗?如果都能很好的回答,那么这篇文章也许对你没有太大帮助。如果不能,别着急,下面就来一一分析解答上面的几个问题。

1. ReentrantReadWriteLock和ReentrantLock的区别?

这个问题大家应该都会回答:ReentrantLock是独占锁,ReentrantReadWriteLock是读写锁。那么这就引申出下一个问题:ReentrantReadWriteLock中的读锁和写锁分别是独占还是共享的?他们之间的关系又是什么样的?要了解透彻这两个问题,分析源码是再好不过的了。

1.1 ReentrantReadWriteLock中的读锁和写锁的实现方式

使用ReentrantReadWriteLock读写锁的方式,会调用readLock()和writeLock()两个方法,看下他们的源码:

public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

可以看到用到了WriteLock和ReadLock两个静态内部类,他们对锁的实现如下:

public static class ReadLock implements Lock, java.io.Serializable {     public void lock() {         sync.acquireShared(1); //共享     }      public void unlock() {         sync.releaseShared(1); //共享     } }  public static class WriteLock implements Lock, java.io.Serializable {     public void lock() {         sync.acquire(1); //独占     }      public void unlock() {         sync.release(1); //独占     } }  abstract static class Sync extends AbstractQueuedSynchronizer {}

看到这里发现了ReentrantReadWriteLock和ReentrantLock的一个相同点和不同点,相同的是使用了同一个关键实现AbstractQueuedSynchronizer,不同的是ReentrantReadWriteLock使用了两个锁分别实现了AQS,而且WriteLock和ReentrantLock一样,使用了独占锁。而ReadLock和Semaphore一样,使用了共享锁。再往下的内容估计看过前面几篇文章的都很熟悉了,独占锁通过state变量的0和1两个状态来控制是否有线程占有锁,共享锁通过state变量0或者非0来控制多个线程访问。在上面的代码中,ReadLock和WriteLock使用了同一个AQS,那么在ReentrantReadWriteLock中又是怎么控制读锁和写锁关系的呢?

1.2 ReadLock和WriteLock共享变量

读写锁定义为:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。

通过这句话很容易联想到通过两个不同的变量来控制读写,当获取到读锁时对读变量+1,当获取懂啊写变量时对写变量+1。但AQS中并没有为ReadLock和WriteLock添加额外的变量,还是通过一个state来实现的。那是怎么做到读写分离的呢?来看看下面这段代码:1。但AQS中并没有为ReadLock和WriteLock添加额外的变量,还是通过一个state来实现的。那是怎么做到读写分离的呢?来看看下面这段代码:

static final int SHARED_SHIFT   = 16; static final int SHARED_UNIT    = (1 << SHARED_SHIFT); static final