09-多线程笔记-2-锁-3-Lock-3-ReadWriteLock
现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
ReetranctLock和Synchronized能够允许一个线程进入多个临界区(可重入),但是不能运行不同线程进入同一个临界区,也就是无法实现多个线程同时读取共享资源;
使用
ReadWriteLock
可以解决这个问题,它保证:
- 只允许一个线程写入(其他线程既不能写入也不能读取);
- 没有写入时,多个线程允许同时读(提高性能)。
ReadWriteLock
有以下三个重要的特性:
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
ReentrantReadWriteLock
JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁.
- 线程进入读锁的前提条件
没有其他线程的写锁;
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
- 线程进入写锁的前提条件
没有其他线程的读锁;
没有其他线程的写锁
Sync
Sync抽象类继承自AQS抽象类,Sync类提供了对ReentrantReadWriteLock的支持。
Sync类内部存在两个内部类,分别为HoldCounter和ThreadLocalHoldCounter,HoldCounter主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程。ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,ThreadLocal类可以将线程与对象相关联。在没有进行set的情况下,get到的均是initialValue方法里面生成的那个HolderCounter对象。
-
读写状态的设计
在
ReentrantReadWriteLock
的内部类Sync
中,定义了若干变量,将同步器状态值state
拆分成两部分,高16位标识读锁,低16位标识写锁;abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 6317671515068378041L; /* * Read vs write count extraction constants and functions. * Lock state is logically divided into two unsigned shorts: * The lower one representing the exclusive (writer) lock hold count, * and the upper the shared (reader) hold count. */ // 将int类型的state(32位),拆分成unsigned shorts(16位) static final int SHARED_SHIFT = 16; // 读锁基本单位(高16位的1) static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 读写锁最大数量 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 写锁的子码,用于从state中计算写锁值 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 省略其他剩余代码 }
ReadLock
-
获取锁
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); // 1. 如果当前线程有获取锁的资格(公平锁只有等待最久的线程可以获取,非公平锁没有限制) // 2. 读锁的数量小于最大数量 // 3. 更新读锁数据,(c+SHARED_UNIT)=(sharedCount(c) + 1) << 16 = (c >>> 16 + 1) << 16,相当于在高16位加1, if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 如果读锁没有被线程获取,直接设置第一个拥有读锁的线程为当前线程(第一个拥有读锁的参数设置是为了优化性能) if (r == 0) { firstReader = current; firstReaderHoldCount = 1; // 如果第一个读锁拥有线程为当前线程,锁重入数加1 } else if (firstReader == current) { firstReaderHoldCount++; } else { // 获取上一个拥有锁的线程的线程数计数器(性能优化) HoldCounter rh = cachedHoldCounter; // 如果没有线程数计数器,或者线程数计数器对应的线程不是当前线程,获取线程空间中保持的线程计数器,并修改锁对象中线程计数器; if (rh == null || rh.tid != LockSupport.getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); // 如果上一个拥有锁的线程是当前线程,并且计数器为0(上一个线程释放了读锁),将线程计数器对象保存到当前线程的空间中。 else if (rh.count == 0) readHolds.set(rh); // 线程计数器加1 rh.count++; } return 1; } // 如果当前线程不具有获取锁资格或比较交换操作失败,则重新获取读锁 return fullTryAcquireShared(current); }
-
释放锁
protected final boolean tryReleaseShared(int unused) { // 获取当前线程 Thread current = Thread.currentThread(); // 如果第一个获取读锁的线程是当前线程 if (firstReader == current) { // assert firstReaderHoldCount > 0; // 如果只获取了一次读锁,丢弃锁对象中缓存的第一个获取读锁线程信息 if (firstReaderHoldCount == 1) firstReader = null; // 如果第一个获取读锁的线程多次获取读锁,获取读锁数减1 else firstReaderHoldCount--; // 如果当前线程不是第一个获取读锁的线程 } else { // 获取上一个拥有读锁的线程对应的线程计数器 HoldCounter rh = cachedHoldCounter; // 如果没有缓存的线程计数器(上一个拥有读锁的线程释放了读锁),或者上一个拥有读锁的线程不是当前线程,从当前线程空间中获取线程计数器 if (rh == null || rh.tid != LockSupport.getThreadId(current)) rh = readHolds.get(); int count = rh.count; // 如果当前线程拥有读锁数不超过1 if (count <= 1) { // 移除当前线程中对应的线程计数器(会造成锁对象中缓存的上一个拥有读锁的线程计数器为null,也就是上一个判断条件) readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } // 如果当前线程多次申请读锁,线程计数器值减1 --rh.count; } // 自旋更新锁状态值 for (;;) { int c = getState(); int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; } }
WriteLock
-
获取锁
@ReservedStackAccess 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) // 如果写锁未被获取(有线程获取了读锁),或者获取写锁的不是当前线程,则获取写锁失败 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; } // 如果锁未被获取(写锁,读锁均未被获取), // 1. 如果当前线程不具有获取写锁的资格(公平锁会有限制),获取比较交换操作失败,则获取写锁失败 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; // 修改锁拥有线程 setExclusiveOwnerThread(current); return true; }
-
释放锁
protected final boolean tryRelease(int releases) { // 如果当前线程不是拥有写锁的线程,抛出异常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; // 如果写锁只被请求一次(未被写锁拥有线程多次申请),则修改写锁拥有线程为null,修改写锁请求值为0(释放写锁),否则写锁释放失败 if (free) setExclusiveOwnerThread(null); setState(nextc); return free; }