Java 并发编程 --- ReentrantReadWriteLock(二)
ReentrantReadWriteLock, 可重入读写锁, 包含读锁与写锁,具体结构如下图:
ReentrantReadWriteLock包含了很多内部类,其中最核心的为Sync、ReadLock、WriteLock
Sync内部类
sync内部类是AQS的实现类,实现了共享锁、独占锁的获取与释放方法,同时将AQS中的state状态值拆分为高16位、低16位(分别代表读锁(共享锁)获取数量、写锁(独占锁)获取数量)
/** int 类型state变量拆分为高16位,,代表共享模式; 低16位,代表独占模式**/
static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; /** 返回共享锁的获取次数(包含重入), 左移16位,低于16位返回0 */ static int sharedCount(int c) { return c >>> SHARED_SHIFT; } /** 返回独占锁的重入次数(独占不重入就为1),c直接执行与操作(就是c本身) */ static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
sync内部类中包含了HoldCounter,ThreadLocalHoldCounter两个静态内部类以及readHolds, cachedHoldCounter,firstReader,firstReaderHoldCount四个不被序列化的变量
/** 记录每个线程持有的读锁数量 */ static final class HoldCounter { int count = 0; // Use id, not reference, to avoid garbage retention final long tid = getThreadId(Thread.currentThread()); }
readHolds : 记录当前线程可重入读锁的数量,仅在Sync构造函数以及readObjec方法中初始化,当前线程的可重入读锁数量降为0时删除(ThreadLocalHoldCounter ThreadLocl子类)
cachedHoldCounter : 记录最后一个线程获取读锁的数量
firstReader : 记录第一个获取读锁的线程
firstReaderHoldCount : 记录第一个线程获取读锁的次数
ReadLock内部类、WriteLock内部类
ReadLock(读锁)、WriteLock(写锁),都实现了Lock接口
锁获取
下图是关于读锁,写锁的获取过程,红色代表读锁,蓝色代表写锁
读锁
读锁(共享锁)的获取,调用的是AQS中的acquireShard方法
1.尝试获取共享锁,即判断tryAcquireShard(arg)的返回值是否小于0,小于0代表没有获取到锁,大于0表示获取到锁;
2.如果小于0(没有获取到读锁(共享锁)),执行doAcquireShared(arg)方法,将线程封装成node存放到AQS队列中, 等待后续的唤醒(获取到锁不会执行该方法)
先来看看tryAcquireShared(int unsed)
protected final int tryAcquireShared(int unused) { /** * 1.如果有其他线程持有写锁,返回-1(即不允许获取读锁(避免读到脏数据)); * * 2.判断获取读锁的线程是否应该被挂起以及尝试获取读锁是否成功; * 关于readerShouldBlock()方法,对于公平锁以及非公平锁,有两种不同的实现 * * 公平锁:判断head节点的next节点是否为空或者next节点对应线程是否是当前线程,如果不为空且是当前线程,不应该被阻塞,否则应该被阻塞。(体现公平性原则) * h != t &&((s = h.next) == null || s.thread != Thread.currentThread())
* * 非公平锁:判断head节点的next节点不为空并且是否是获取写锁的节点,如果是获取写锁的节点,不能与获取写锁的节点争抢,获取读锁失败,应该被阻塞。
* (读锁不应该与写锁抢占资源)(h = head) != null &&(s = h.next) != null &&!s.isShared() &&s.thread != null; * * 3.如果获取读锁成功; * 3.1.如果读锁的获取次数(包括重入)为0(即没有线程获取读锁,或者获取读锁的线程刚释放锁),则将firstReader设置为当前线程,firstReaderHoldCount赋值为1. * 3.2.如果读锁获取次数不为0,firstReader为当前线程,firstReaderHoldCount加1(重入) * 3.3.如果读锁获取次数不为0,第一次获取读锁的线程不是当前线程,更新最后一次获取读锁的线程,以及该线程获取读锁的次数(包含重入) * * 4.条件2不满足时,执行fullTryAcquireShared(current)方法
*
*
* 代码截图需要截断,故直接贴的源码。
*/ 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); }
关于fullTryAcquireShared(Thread current)方法,会去再次尝试获取读锁
/** * 这段代码是为了处理获取读锁的线程不应该被挂起,但是CAS操作失败的获取锁线程,增加CAS成功的机会
* * 1.获取锁的状态值
* * 2.判断写锁是否被占用,如果被占用的线程不是当前线程,return -1;
* * 3.如果没有线程获取读锁,且写锁的获取线程需要被挂起(该步骤是为了确保可重入锁成功)
* * 3.1.判断是否第一个获取读锁的线程时候是当前线程,如果是执行后续的CAS操作
* * 3.2.如果不是,此时rh为null,获取对应的缓存,如果缓存为空,或者对应线程不是当前线程,这个地方会执行初始化的操作
* * 3.3.如果缓存不为空且对应线程是当前线程,会执行后续CAS操作,否则返回-1
* * 4.执行后续CAS获取锁操作(与tryAcquireShared一样) */ final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { int c = getState(); if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; // else we hold the exclusive lock; blocking here // would cause deadlock. } else if (readerShouldBlock()) { // Make sure we're not acquiring read lock reentrantly if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } }
当获取读锁失败,会去执行doAcquireShared(int arg) 方法,将线程信息封装成队列,保存在AQS队列中(与可重入锁ReentrantLock一样)
写锁
写锁的获取,调用的是AQS中的acquire方法,其中tryAcquire(arg)是在ReentrantWriteReadLock中实现的。
关于tryAcquire方法,主要进行以下步骤:
protected final boolean tryAcquire(int acquires) { /*
* 1.获取锁的状态值,获取写锁的获取次数;
*
* 2.如果c!=0 && w==0或者c!=0 && current != getExclusiveOwnerThread(), 即有线程持有读锁或者有线程持有写锁,但持有写锁的线程不是当前线程,返回false;
*
* 3.如果2中不满足,则获取锁成功,设置锁的状态值(其实到这里就表示已经表示重入写锁成功了,不需要进行CAS操作,如果不是写锁的重入,或者说获取写锁失败的话,
* 或被2中的判断拦截);
*
* 4.如果2中条件不满足,会判断写锁获取线程是否应该被挂起或者CAS操作时候会失败,如果2中条件满足则不会执行该步骤,会直接在2中返回true/false;
*
* 4.1.判断锁是否应该被挂起操作,需要判断是公平锁还是非公平锁
*
* 公平锁:h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
*
* 首先判断AQS队列是否为空,其次head节点的next节点是否为空获取next节点对应的线程是否是当前线程,如果队列不为空并且next对应线程不是当前线程
* (即队列中有线程等待),返回true,需要阻塞等待。
*
* 非公平锁 : 直接返回false, 因为是非公平锁,不遵循先到先得策略,可以直接CAS操作去抢占锁。
* * 4.2.如果需要被挂起,或者CAS操作失败,返回false(表明获取锁失败) */ 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) 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; }
如果获取写锁失败,则会将当前线程信息封装成一个Node对象放到CAS队列中, 具体可查看ReentrantLock中的说明。
锁的释放
读锁
读锁的释放是调用了AQS中的releaseShared(int arg) 方法
对于tryReleaseShared(arg)方法,是在ReentrantReadWriteLock的内部类Sync中实现的
1.获取当前线程,判断第一个获取读锁的线程是否是当前线程,如果是并且第一个获取读锁的线程获取读锁的次数不为1,进行减1操作,如果为1,直接将第一个获取读锁的变量置为空 2.获取当前线程对应的缓存信息,没有就初始化一个,判断对应缓存信息中保存的获取读锁数量,小于等于1就remove掉,否则执行减1操作 3.通过自旋CAS操作,设置锁的状态值,并判断是否是等于0,为0表示读锁与写锁都是访问完毕,会去唤醒后续线程。
对于释放锁成功,也就是说读锁与写锁都释放完了,会执行doReleaseShared()操作唤醒后续节点
写锁
读锁的释放是调用了AQS中的releaseShared(int arg) 方法
对于release(arg)方法,是在ReentrantReadWriteLock的内部类Sync中实现的,相对来说比较简单
1.判断获取写锁的线程是否是当前线程,如果不是抛出异常 2.判断写锁的获取次数是否为0,如果是,设置获取写锁的线程为null 3.设置锁的状态值,返回true/false(写锁是否都释放)
如果没有线程获取锁,会去唤醒后续节点(唤醒操作与ReentrantLock中一直)