ReentrantReadWriteLock

93 阅读6分钟

ReentrantReadWriteLock简介

  • ReentrantReadWriteLock中的Sync继承了AQS,使用AQS的共享模式作读锁,独占模式作写锁

  • 对于AQS中的state值也被一分为二,高16位用于共享模式表示读锁获取次数(包括重入),低16位用于独占模式表示写锁重入次数

Sync属性

		
	//共享模式使用的state位
	static final int SHARED_SHIFT   = 16;
	
	//线程拿到读锁时state值的增加量,实际上就是高16位增加一
	//1 0000 0000 0000 0000
	static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
	
	//写锁和读锁的最大获取次数
	//1111 1111 1111 1111
	static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
	
	//EXCLUSIVE_MASK: 1111 1111 1111 1111 
	static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

	//读锁获取次数(包括重入):
	//	   例如state:0000 0000 0000 0011 0000 0000 0000 0100
	//右移16位结果为: 11 当前读锁获取次数为3
	static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
	
	//写锁重入次数
	//	   例如state:0000 0000 0000 0011 0000 0000 0000 0100
	//EXCLUSIVE_MASK:                    1111 1111 1111 1111 
	//结果为: 100 当前写锁重入次数为4
	static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

	//记录线程对读锁的重入次数
	static final class HoldCounter {
		
		//读锁重入次数
		int count = 0;
		// Use id, not reference, to avoid garbage retention
		final long tid = getThreadId(Thread.currentThread());
	}

	//通过继承ThreadLocal来保存HoldCounter,关于ThreadLocal见ThreadLocal源码阅读
	static final class ThreadLocalHoldCounter
		extends ThreadLocal {
		public HoldCounter initialValue() {
			return new HoldCounter();
		}
	}
	private transient ThreadLocalHoldCounter readHolds;

	//最后一个获取读锁线程的HoldCounter  	
	private transient HoldCounter cachedHoldCounter;
	
	//第一个获得读锁的线程(未释放读锁),这里的第一个并不是从全局的角度的第一个,当所有线程释放读锁后,再拿锁的线程会成为新的firstReader
	private transient Thread firstReader = null;
	
	//第一个获得读锁的线程的HoldCounter
	private transient int firstReaderHoldCount;
	
	/*
	   cachedHoldCounter  firstReader firstReaderHoldCount 在干什么?
	   举例: 此时读锁写锁都空闲,A线程获取了读锁,firstReader和firstReaderHoldCount更新为A线程和A的HoldCounter
	         B线程又来获取读锁,cachedHoldCounter更新B线程的HoldCounter
			 A线程释放了锁,firstReader更新为null
			 C线程来获取读锁,cachedHoldCounter更新C线程的HoldCounter
			 B和C线程都释放了读锁,D线程来获取锁,firstReader和firstReaderHoldCount更新为D线程和D的HoldCounter
		
		firstReader 记录的是无锁状态下来获取读锁的线程,当该线程释放读锁后,firstReader更新为null,
		只有在读锁被全部释放之后,新的线程来获取读锁才会更新firstReader
		
		cachedHoldCounter记录的是有锁状态下来获取读锁的线程,就会把该线程HoldCounter记录到cachedHoldCounter
	*/
	
	

	//初始化readHolds和state值
	Sync() {
		readHolds = new ThreadLocalHoldCounter();
		setState(getState()); // ensures visibility of readHolds
	}

	
	/*
	  获取锁前是否阻塞,对于公平锁和非公锁都重写了此方法
	*/
	abstract boolean readerShouldBlock();

	abstract boolean writerShouldBlock();
	
	

  • 类似于ReentrantLock中的公平和非公平机制,非公平锁会在在线程获得锁前先检测一下阻塞结点中是否有其他结点等待
		
	/*
       非公平锁:读锁获取前不需要阻塞,直接去获取,无论阻塞队列中是否有其他结点
				对于写锁,如果阻塞队列的head.next是共享结点,即写锁结点,这时需要阻塞
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

    /*
		公平锁:读锁和写锁获取前都要先看阻塞队列是否有其他线程等待
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }
	

读锁的加锁和解锁

  • 共享模式: lock() -> acquireShared() -> tryAcquireShared() unlock() -> releaseShared() -> tryReleaseShared()

  • Sync中重写了tryReleaseShared()和tryReleaseShared(),关于共享模式见AQS源码阅读(三)

  • tryAcquireShared()失败,线程会把自己封装成Node结点放入阻塞队列阻塞等待

  • tryReleaseShared()成功就会去阻塞队列唤醒线程

		
	protected final int tryAcquireShared(int unused) {
		
		Thread current = Thread.currentThread();
		
		//当前的state值
		int c = getState();
		
		//exclusiveCount(c) != 0:写锁已经被持有
		//getExclusiveOwnerThread() != current:持有写锁的不是自己,放弃争夺读锁
		if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
			return -1;
		
		//当前读锁获取次数
		int r = sharedCount(c);
		
		//readerShouldBlock():争夺读锁前是否阻塞
		//r < MAX_COUNT:是否达到锁的最大获取次数
		// compareAndSetState(c, c + SHARED_UNIT):CAS操作修改state,就是拿锁
		if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
			
			//此时已经拿到了读锁
			
			//当前线程是firstReader
			if (r == 0) {
				firstReader = current;
				firstReaderHoldCount = 1;
				
			//firstReader的线程在重入读锁
			} else if (firstReader == current) {
				firstReaderHoldCount++;
			
			//此时线程不是firstReader去更新cachedHoldCounter
			} else {
				HoldCounter rh = cachedHoldCounter;
				
				//cachedHoldCounter中不是自己的HoldCounter
				if (rh == null || rh.tid != getThreadId(current))
					
					//从自己线程的ThreadLocalMap中取出自己的HoldCounter,更新到读写锁中
					cachedHoldCounter = rh = readHolds.get();
					
				//cachedHoldCounter记录的是自己,但是count为0,为什么?
				//假如B线程获取了读锁,又释放了读锁,那么它的HoldCounter中count就会为0
				//恰好在B拿锁解锁这段时间没有线程获取读锁,也就没有线程更新cachedHoldCounter,就会出现这种情况				
				else if (rh.count == 0)
					readHolds.set(rh);
				
				//拿到读锁了把count加1
				rh.count++;
			}
			
			//返回一个大于0的数表示获取到了共享锁
			//为什么不返回state?
			//因为state是int类型,int类型是有符号数,用补码表示时首位为1就是负数了
			return 1;
		}
		
		//情况一:争夺读锁前需要阻塞
		//情况二:达到锁的最大获取次数
		//情况三:CAS竞争失败
		
		return fullTryAcquireShared(current);
	}

	/*
		为什么需要阻塞还要来争夺读锁? 有可能是读锁在重入
	*/
	final int fullTryAcquireShared(Thread current) {
		
		HoldCounter rh = null;
		
		//自旋
		for (;;) {
			int c = getState();
			
			//已经有线程获取了写锁
			if (exclusiveCount(c) != 0) {
				
				//获取写锁的线程不是自己,拿锁失败
				if (getExclusiveOwnerThread() != current)
					return -1;
				
			} else if (readerShouldBlock()) {
				
				//写锁没有获取,如果readerShouldBlock() == true,此时阻塞队列中head.next是共享结点
				//在此时,阻塞队列中的head.next要获取写锁,因此获取读锁的线程要阻塞,优先让写锁去获取
				
				//在这有两种线程:持有读锁来重入的线程可以拿锁,没有读锁的返回-1
				
				//情况一:此时的线程是firstReader,一定持有读锁,可以重入
				if (firstReader == current) {
					// assert firstReaderHoldCount > 0;
					
				//情况二:此时的线程不是firstReader,那就查看它的HoldCounter判断是否持有读锁
				} else {
					
					if (rh == null) {
						rh = cachedHoldCounter;
						
						//cachedHoldCounter中记录的不是当前线程
						if (rh == null || rh.tid != getThreadId(current)) {
							
							//到线程的ThreadLocalMap中去取
							rh = readHolds.get();
							
							//当前线程不持有读锁
							if (rh.count == 0)
								readHolds.remove();
						}
					}
					//当前线程不持有读锁,返回-1
					if (rh.count == 0)
						return -1;
				}
			}
			
			//到这里的线程有:线程需要阻塞,但线程是firstReader,一定持有读锁,来重入读锁的
			//               线程需要阻塞,但线程的HoldCounter大于0,一定持有读锁,来重入读锁的
			//               线程不需要阻塞,来抢锁了
			
			//已经达到获取锁次数最大值
			if (sharedCount(c) == MAX_COUNT)
				throw new Error("Maximum lock count exceeded");
			
			//获取读锁
			if (compareAndSetState(c, c + SHARED_UNIT)) {
				
				//和上面获取读锁成功一样,来设置firstReader,firstReaderHoldCount或者cachedHoldCounter
				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;
			}
		}
	}
	
	protected final boolean tryReleaseShared(int unused) {
		Thread current = Thread.currentThread();
		
		//当前线程是firstReader
		if (firstReader == current) {
			
			//重入次数为1,此次将彻底释放读锁
			if (firstReaderHoldCount == 1)
				firstReader = null;
			
			//还将持有读锁
			else
				firstReaderHoldCount--;
		} else {
			
			//先查看cachedHoldCounter记录的是不是自己
			HoldCounter rh = cachedHoldCounter;
			if (rh == null || rh.tid != getThreadId(current))
				
				//从自己线程的ThreadLocalMap中取出自己的HoldCounter
				rh = readHolds.get();
			int count = rh.count;
			if (count <= 1) {
				
				//自己不再持有读锁,从自己线程的ThreadLocalMap中移除HoldCounter
				readHolds.remove();
				if (count <= 0)
					throw unmatchedUnlockException();
			}
			
			//持有读锁数量减一
			--rh.count;
		}
		
		//自旋
		for (;;) {
			
			//state值
			int c = getState();
			
			//释放锁之后的state值
			int nextc = c - SHARED_UNIT;
			
			//CAS修改state值
			if (compareAndSetState(c, nextc))
				
				//如果释放锁之后state值为0,此时既没有线程持有写锁,也没有线程持有读锁,当前释放锁的线程会去唤醒阻塞队列中的线程
				return nextc == 0;
		}
	}
	

写锁的加锁和解锁

  • 独占模式: lock() -> acquire() -> tryAcquire() unlock() -> release() -> tryRelease()

  • Sync中重写了tryRelease()和tryRelease(),关于独占模式见AQS源码阅读(一)

  • tryAcquire()失败,线程会把自己封装成Node结点放入阻塞队列阻塞等待

  • tryRelease()成功就会去阻塞队列唤醒线程

		

	protected final boolean tryAcquire(int acquires) {
		
		Thread current = Thread.currentThread();
		
		//state值
		int c = getState();
		
		//写锁的重入次数
		int w = exclusiveCount(c);
		
		//已经有线程获取锁
		if (c != 0) {
			
			//c != 0 && w== 0:有线程持有读锁
			//current != getExclusiveOwnerThread():持有写锁的不是自己
			if (w == 0 || current != getExclusiveOwnerThread())
					
				/*
					一种奇妙的情况:
					如果某个线程获取读锁后,再获取写锁,来到这里发现,有线程持有读锁(尽管持有读锁的是自己),返回了false
					线程会把自己送入阻塞队列阻塞,此时线程持有写锁,没有其他线程能获取锁,更不可能来唤醒自己了
					这个线程将永远阻塞
				*/
				return false;
			
			//拿锁后重入次数超过限制
			if (w + exclusiveCount(acquires) > MAX_COUNT)
				throw new Error("Maximum lock count exceeded");
			
			//重入锁,此时不需要CAS,因为拿锁的正是自己
			setState(c + acquires);
			return true;
		}
		
		//writerShouldBlock():获取写锁时是否阻塞 
		//compareAndSetState():CAS修改state值,就是拿锁
		if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
			return false;
		
		//设置自己为持有写锁的线程
		setExclusiveOwnerThread(current);
		return true;
	}
	
	protected final boolean tryRelease(int releases) {
		
		//当前线程没有持有写锁,不能释放
		if (!isHeldExclusively())
			throw new IllegalMonitorStateException();
		
		//释放写锁之后的state值
		int nextc = getState() - releases;
		
		//是否完全释放
		boolean free = exclusiveCount(nextc) == 0;
		
		//完全释放写锁后,更新持有锁线程为null
		if (free)
			setExclusiveOwnerThread(null);
		
		//CAS修改state值
		setState(nextc);
		return free;
	}