1. ReadWriteLock接口
ReadWriteLock是一个java接口,它并没有继承Lock接口。提供了readLock()和writeLock(),分别返回一个读锁和写锁。
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
2. ReentrantReadWriteLock使用
ReentrantReadWriteLock类内部定义了静态内部类ReadLock和WriteLock。同时持有 readLock和writerLock对象。
public class ReadWriterLockUsage { private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private Map<String, String> map = new HashMap<>(); public String get(String key) { try { reentrantReadWriteLock.readLock().lock(); System.out.println(Thread.currentThread().getName() + " readLock"); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } return map.get(key); } finally { reentrantReadWriteLock.readLock().unlock(); System.out.println(Thread.currentThread().getName() + " readUnlock"); } } public void put(String key, String value) { try { reentrantReadWriteLock.writeLock().lock(); System.out.println(Thread.currentThread().getName() + " writeLock"); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } map.put(key, value); } finally { reentrantReadWriteLock.writeLock().unlock(); System.out.println(Thread.currentThread().getName() + " writeUnLock"); } } public static void main(String[] args) { ReadWriterLockUsage usage = new ReadWriterLockUsage(); for (int i = 0; i < 2; i++) new Thread() { @Override public void run() { super.run(); while (true) usage.put("", ""); } }.start(); for (int i = 0; i < 2; i++) new Thread() { @Override public void run() { super.run(); while (true) usage.get(""); } }.start(); } }
运行结果可能如下
Thread-0 writeLock Thread-0 writeUnLock Thread-0 writeLock Thread-0 writeUnLock Thread-1 writeLock Thread-1 writeUnLock Thread-2 readLock Thread-3 readLock Thread-2 readUnlock Thread-3 readUnlock Thread-0 writeLock Thread-0 writeUnLock Thread-0 writeLock Thread-0 writeUnLock Thread-1 writeLock Thread-1 writeUnLock Thread-1 writeLock Thread-1 writeUnLock
通过观察结果我们可以发现writeLock同一时刻只能被一个线程获得。而writeLock同一时刻可以被多个线程同时获得。
3. 源码解析
1. 锁状态
通过ReentrantLock的源码解析我们了解到,锁的状态由 private volatile int state来控制。每当线程获取到了锁 state会加1,每当线程释放了锁state会减1。当state=0表示锁当前处于非上锁状态。state定义在AbstractQueuedSynchronizer中。ReentrantLock持有一个AbstractQueuedSynchronizer对象。
那ReentrantReadWriteLock内部的锁是怎么实现的呢?我们知道ReentrantReadWriteLock内部有一个ReadLock和WriteLock。那么他们之间是完全独立的锁吗?如果不是独立的锁,那么state怎么来标识读写锁加锁的状态和次数呢?
事实上ReadLock和WriteLock是共用同一个AQS对象。AQS的state的值可以标识锁的状态。在读写锁的AQS实现中,int类型的state,我们知道int类型在计算机中有32位。它在读写所中,高16位标识读锁状态。低16位表示写锁状态。
static final int SHARED_SHIFT = 16; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//值为FFFF //当前读锁(共享锁)被获取的数量 c >>> SHARED_SHIFT表示向右移动16个单位,结果是int的高16位 static int sharedCount(int c) { return c >>> SHARED_SHIFT; } //写锁(独占锁)被获取的数量,低16位 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
2.WriteLock源码分析
首先我们来分析写锁的源码,因为写锁相对读锁更简单一些,写锁和ReentrantLock都是独占锁,所以我们先来分析它。还是从lock()开始分析
public static class WriteLock implements Lock, java.io.Serializable { private final Sync sync; protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } public void lock() { sync.acquire(1); } }
我们知道sync的acquire(1)是个模板方法,先调用tryAcquire(1)方法如果获取成功,直接返回,如果获取失败,把线程封装成节点,加入队列,并且通过自旋来获取锁。在ReentrantLock源码分析中有详细讲解,读者如果不熟悉可以,先看ReentrantLock源码分析文章。我们重点来看下tryAcquire(1)在ReentrantReadWriteLock中的实现
protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. *(如果读锁被获取 或者 写锁被获取而且获取到的线程不是当前线程,获取写锁失败) * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) *(如果锁的数量超过限制,获取失败) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. *(否则,如果该线程是可重入获取或队列策略允许,则该线程有资格进行锁定。 如果是这样,更新状态并设置所有者。) */ Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) //如果 c != 0 and w == 0 表示读锁被线程占有了,获取锁失败 if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current);//获取成功了 把当前线程设置为锁的拥有者线程 return true; }
如果当前读锁被获取了,再去获取写锁会失败。简单吧,如果失败了,走的还是ReentrantLock那一套
3. ReadLock源码解析
我们知道读锁是共享锁,同一时刻可以被多个线程同时获取。接下来我们来分析下。还是从lock()方法开始
public static class ReadLock implements Lock, java.io.Serializable { private final Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } /** * Acquires the read lock. * * <p>Acquires the read lock if the write lock is not held by * another thread and returns immediately. * * <p>If the write lock is held by another thread then * the current thread becomes disabled for thread scheduling * purposes and lies dormant until the read lock has been acquired. */ public void lock() { //如果写锁没有被其他线程占有立马获取到读锁 //如果写锁被其他线程占有,那么当前线程会阻塞直到读锁被获取到 sync.acquireShared(1); } }
我们来看下sync.acquireShared(1)方法
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0)//如果尝试获取共享锁失败 doAcquireShared(arg);
我们先来看下doAcquireShared()吧,如果是独占锁尝试获取失败,之后的流程我们是比较清楚的,我们可以对比观察他们之间的区别
private void doAcquireShared(int arg) { //加入到AQS队列中 和独占锁是一样的 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) {//通过无限循环实现自旋功能和独占锁也是一样的 final Node p = node.predecessor(); if (p == head) {//如果是队首节点 int r = tryAcquireShared(arg); if (r >= 0) {//如果获取成功 //获取成功共享锁和独占锁是有区别的,独占锁获取成功直接return。 //共享锁如果获取成功它会告诉下一个等待获取共享锁线程去获取共享锁 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
我们再来看下tryAcquireShared()的实现吧
protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. Otherwise, this thread is eligible for * lock wrt state, so ask if it should block * because of queue policy. If not, try * to grant by CASing state and updating count. * Note that step does not check for reentrant * acquires, which is postponed to full version * to avoid having to check hold count in * the more typical non-reentrant case. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } return fullTryAcquireShared(current); }
关于锁降级
锁降级是指在获取写锁的同时,读锁也能获取到,当然这种情况只限于同一个线程。降级是对写锁而言,对于读锁个人觉得应该是锁升级。附上java源码的例子
class CachedData { Object data; volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); try { // 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 rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { use(data); } finally { rwl.readLock().unlock(); } } }
下面那个读锁其实被升级为独占锁了,同一时刻只能被一个线程获取