java并发-读写锁ReentrantReadWriteLock

基础

  • 读写锁的定义:对于读锁的获取,即使已经有线程获取了读锁,当前线程也可以获取成功;对于写锁的获取,如果已有线程获取了写锁或者其他线程获取了读锁,那么写锁获取失败;读锁写锁是互斥的。

demo

基于非线程安全的HashMap和读写锁实现的线程安全的缓存容器。

public class Cache {
        private HashMap map;
        private Lock writeLock;
        private Lock readLock;
        public Cache(){
            ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
            writeLock = lock.writeLock();
            readLock = lock.readLock();
        }
        public Object get(String key){
            readLock.lock();
            try {
                return map.get(key);
            }finally {
                readLock.unlock();
            }
        }
        public void put(String key, Object value){
            writeLock.lock();
            try{
                map.put(key, value);
            }finally {
                writeLock.unlock();
            }
        }
    }

读写锁(以ReentrantReadWriteLock为例)实现分析

状态变量设计

显然读写锁需要共用同一个状态变量(使用AQS作为底层实现),所以我们需要把AQS中的state变量分为两部分一部分用于维护读线程的状态信息,另一部分维护写线程的状态信息。
ReentrantReadWriteLock 中将state变量的低16位用于写线程,高16位用于读线程。所以假设当前同步变量状态位S,那么读状态+1时, S = S + (1<<16);当写状态+1时,S = S+1;
推论:当 S !=0 时,且写状态(S&0X0000FFFF)等于0时,则读状态大于0,即读锁已被获取。

写锁的获取与释放

  • 何时失败:当有线程获取了读锁(可能是当前线程,因为不能锁升级);或者其他线程已经获得了写锁
  • 何时重入:当前线程已经占有写锁。
  • 参照英文注释
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);//获取写状态(同步变量的低16位,位运算获取)
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //这里代表有线程获得了读锁(w==0 & c != 0)
                //或者有其他线程获得了写锁(current != getExclusiveOwnerThread())
                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;
        }

读锁的获取与释放

参照英文注释

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

锁降级

定义:把持写锁,再获取到读锁,再释放写锁。
意义:写锁释放之前把持读锁,可以保证数据的可见性,应用场景?
下午书中示例

public void processData() {
    readLock.lock();
    if (!update) {
    // 必须先释放读锁
    readLock.unlock();
    // 锁降级从写锁获取到开始
    writeLock.lock();
    try {
      if (!update) {
      // 准备数据的流程(略)
      update = true;
     }
    readLock.lock();
    } finally {
      writeLock.unlock();
    }
    // 锁降级完成,写锁降级为读锁
    }
  try {
      // 使用数据的流程(略)
   } finally {
      readLock.unlock();
  }
}

问题

  • 为什么获取读写锁读锁被获取之后,不能获取写锁?
    因为要保证写锁的操作对读锁可见,数据可见性。
  • 为什么不能锁升级,为什么可以锁降级?
  • 为什么需要锁降级?

你可能感兴趣的:(java并发-读写锁ReentrantReadWriteLock)