zoukankan      html  css  js  c++  java
  • 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;
      }
      

    参考文档

  • 相关阅读:
    Java简单游戏开发之碰撞检测
    Android应用程序在新的进程中启动新的Activity的方法和过程分析
    Android应用程序组件Content Provider的共享数据更新通知机制分析
    Android应用程序组件Content Provider在应用程序之间共享数据的原理分析
    VS2005检查内存泄露的简单方法
    iOS如何让xcode自动检查内存泄露
    wf
    VC++ListBox控件的使用
    textoverflow:ellipsis溢出文本显示省略号的详细方法
    VC编写代码生成GUID并转换为CString字符串
  • 原文地址:https://www.cnblogs.com/donfaquir/p/13846112.html
Copyright © 2011-2022 走看看