09-多线程笔记-2-锁-3-Lock-3-ReadWriteLock

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。

ReetranctLock和Synchronized能够允许一个线程进入多个临界区(可重入),但是不能运行不同线程进入同一个临界区,也就是无法实现多个线程同时读取共享资源;

使用ReadWriteLock可以解决这个问题,它保证:

  • 只允许一个线程写入(其他线程既不能写入也不能读取);
  • 没有写入时,多个线程允许同时读(提高性能)。

ReadWriteLock有以下三个重要的特性:

  • 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
  • 重进入:读锁和写锁都支持线程重进入。
  • 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

ReentrantReadWriteLock

JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁.

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;
    }
    

参考文档

posted @ 2020-10-20 13:52  donfaquir  阅读(149)  评论(0编辑  收藏  举报