在上一篇博客并发编程--读写锁ReadWriteLock和ReentrantReadWriteLock(一)中我们简单介绍了一下读写锁的相关知识,接下来来我们介绍一下读锁的实现机制,简单的来说读锁就是一个独占锁,如果看过ReentrantLock相关的知识,应该会对独占锁的实现有一些简单的理解,简单来说独占锁的实现是当锁标识位state为0时,当前线程获取锁,并将state进行加一操作,其他线程来获取锁时会发现state此时不为0,则将当前线程添加到FIFO队列中,并将线程进行阻塞,线程释放锁时会将state进行减一操作,并唤醒FIFO队列中阻塞的线程来竞争锁。
接下来我们看看读锁是如何实现的。 lock的操作是如果获取锁则将锁标识位state进行加一操作。
public void lock() {
sync.acquire(1);//锁标识位加一
}
acquire中会尝试获取锁,如果获取锁失败则将线程添加到FIFO队列中挂起
public final void acquire(int arg) {//tryAcquire(arg) 尝试获取锁,如果获取锁失败调用acquireQueued将线程添加FIFO队列中并挂起 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }在tryAcquire中判断是否可以获取锁,这里有一点不同的是如果一个读线程获取了锁,则锁标识位state加上65536
protected final boolean tryAcquire(int acquires) {//获取当前线程 Thread current = Thread.currentThread();//获取锁标识位值 int c = getState();//与65535进行与运算,如果此时读线程获取锁则c=65536,此时w=0 int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0)//当此时有写锁或者读锁是都返回false 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; }
如果读线程获取了锁则此时state值为65536,w=exclusiveCount(c)获取的值为0,则返回false,当两个读线程竞争锁时,w=exclusiveCount(c) 值w=c,但current != getExclusiveOwnerThread()为true则返回false。其实读锁是一个独占锁,当state值不为0且运行线程不是当前线程则就无法获取锁。
释放写锁:
public void unlock() { sync.release(1);//将state值进行减一操作 }
release中的操作是释放锁并唤起其他阻塞的线程
public final boolean release(int arg) {//释放锁 if (tryRelease(arg)) { Node h = head;//唤起其他阻塞线程 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively()) throw new IllegalMonitorStateException();//将state进行减release操作 int nextc = getState() - releases;//当nextc等于0时将当前执行线程设置为null boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null);//修改锁标识位 setState(nextc); return free; }这样就完成了锁的释放。
接下来我们来看看读锁是如何获取锁的,并且读锁是共享锁,我们来了解一下读锁是如何实现共享锁的。
调用lock方法获取锁
public void lock() {//获取锁 sync.acquireShared(1); }acquireShared会尝试获取锁,如果无法获取锁时则将当前线程添加到FIFO队列中,并将线程阻塞
public final void acquireShared(int arg) {//尝试获取锁 if (tryAcquireShared(arg) < 0)//将线程添加到FIFO队列中 doAcquireShared(arg); }
tryAcquireShared中的操作是如果获取共享锁的话会将state设置为state = state +65536,这样下一个读锁过来的时候state>>>16值一般会小于65535的,这样就实现了读锁不会被读锁阻塞了。exclusiveCount(0)的操作是c&65535,65536&65535等于0则是可以获取读锁,不然就是写锁,阻塞当前阻塞。
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread();//获取锁标识位值 int c = getState();//如果是不存在锁exclusiveCount(c)等于0,如果是存在读锁exclusiveCount(c)65536&65535等于0,如果存在写锁exclusiveCount(c)返回值不为0 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT &&//最终设置锁标识位位state=state+65536,这个是与写锁不同的地方 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); }
锁的释放
读锁在设置锁时将state设置为state=state+65536,那样释放锁时state设置为state = state-65536,这样就完成了锁的释放操作。