ReadWriteLock源码阅读

简介

  1. 读写锁

     同一个资源有读写操作,但是读操作要比写操作频繁(读多写少的情况)。保证数据一致性的话需要进行同步,那么需要对写、读操作互斥加锁,但是真实场景却是读不需要互斥、写需要进行互斥,就诞生了读写锁ReadWriteLock。
    
  2. 类结构

    public interface ReadWriteLock {
        /**
         * Returns the lock used for reading.
         *
         * @return the lock used for reading
         */
        Lock readLock();
    
        /**
         * Returns the lock used for writing.
         *
         * @return the lock used for writing
         */
        Lock writeLock();
    }
    
  3. 实现类

    关键实现类--ReentrantReadWriteLock

    使用方法:

    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); //  创建 -- 可以传true、false 实现公平锁和非公平锁
    ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); // 读锁
    ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); // 写锁
    

使用场景

上代码

class MyTest{

    // 创建读写锁、进行初始化 -- 可以传true、false 实现公平锁和非公平锁  类似ReentrantLock
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true);
    // ReadLock 实现的是共享锁
    ReentrantReadWriteLock.ReadLock readLock = rwl.readLock();
    // WriteLock 实现的是排他锁
    ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();

    // 竞争资源
    private Map<String,String> map = new HashMap<>();

    /**
     * 读操作加读锁
     * 获取条件:
     *     没有其他线程已获取写锁
     *     没有写请求或有当前线程同时是写请求
     */
    public String get(String key){
        readLock.lock();
        try{
            return map.get(key);
        }finally {
            readLock.unlock();
        }
    }

    /**
     * 写操作加写锁
     * 获取条件: 没有其他线程读、写
     */
    public void set(String key , String value){
        writeLock.lock();
        try{
            map.put(key,value);
        }finally {
            writeLock.unlock();
        }
    }
}

ReentrantReadWriteLock具有以下三个重要的特性:

  • 公平、非公平选择性:构造传参为true、false实现公平非公平
  • 可重入锁:同一线程可以多次获取,有一个count计数器
  • 锁降级:获取写锁后可以再去获取读、然后释放写锁,完成锁降级

源码分析

众所周知,所有的锁都是通过AQS实现的,即state属性标记当前锁状态,Node队列为等待队列进行自旋等待或者Park中断等待唤醒;

state是如何标记读写锁的

​ 从源码角度来进行分析state标志位如何标记读写锁

	volatile int state; //state 为 int类型的整数 4个字节 32位
	0000 0000 0000 0000 0000 0000 0000 0000
  	 高16位代表读锁    |   低16位代表写锁
        state = 0 代表锁空闲   写锁操作 +1   读锁操作 65535 + 1
        // 高低16位、静态变量
        static final int SHARED_SHIFT   = 16;
	// 上读锁需要在高16位操作,也就是需要添加一个17位的数
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
	// 读、写锁的最大数量
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;	
	// state & 这个数,能获取低16位(写锁)的数值
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
	// 获取读锁 -- 右移动16位,高位补0 , 获取读锁状态
	static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
	// 获取写锁 高16位补0,  & EXCLUSIVE_MASK 即可
	static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

独占锁

获取锁

加锁过程分为两步,1:尝试获取锁,2:没获取到锁自选等待或者进入队列

  1. 写锁中尝试获取锁调用的是AQS中的tryAcquire(1)的方法,实现方法为ReentrantReadWriteLock.Synv.tryAcquire()
protected final boolean tryAcquire(int acquires) {
  Thread current = Thread.currentThread();
  // 获取state属性值状态
  int c = getState();
  // 获取写锁被获取的次数
  int w = exclusiveCount(c);
  // state不为0 代表由读锁或者写锁
  if (c != 0) {
    // 写锁的获取次数为0  或 当前线程不等于独占线程   -- 都不可在获取锁
    if (w == 0 || current != getExclusiveOwnerThread())
      return false;
    // 写锁获取的最大数量 则抛出异常
    if (w + exclusiveCount(acquires) > MAX_COUNT)
      throw new Error("Maximum lock count exceeded");
    // 写锁写入成功
    setState(c + acquires);
    return true;
  }
  // state 如果为0 代表没有任何线程获取锁
  // 判断是否有阻塞队列、然后通过CAS操作更新state数值
  if (writerShouldBlock() ||
      !compareAndSetState(c, c + acquires))
    return false;
  // 设置独占线程
  setExclusiveOwnerThread(current);
  return true;
}
  1. 未获取到锁的线程,会将自身自选等待或者加入等待,调用的是acquireQueued(final Node node, int arg) 方法

    // 将当前线程封装为Node队列节点
    private Node addWaiter(Node mode) {
      // Node节点创建
      Node node = new Node(Thread.currentThread(), mode);
      // node添加为尾节点
      Node pred = tail;
      if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
          pred.next = node;
          return node;
        }
      }
      enq(node);
      return node;
    }
    // 自旋等待
     final boolean acquireQueued(final Node node, int arg) {
       boolean failed = true;
       try {
         boolean interrupted = false;
         for (;;) {
           final Node p = node.predecessor();
           if (p == head && tryAcquire(arg)) {
             setHead(node);
             p.next = null; // help GC
             failed = false;
             return interrupted;
           }
           // shouldParkAfterFailedAcquire将前置节点设置为挂起 
           if (shouldParkAfterFailedAcquire(p, node) &&
               parkAndCheckInterrupt()) // parkAndCheckInterrupt 当前线程挂起
             interrupted = true;
         }
       } finally {
         if (failed)
           cancelAcquire(node);
       }
     }
    
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      // 前置节点状态
      int ws = pred.waitStatus;
      // 前继节点完成资源的释放或者中断后,会通知当前节点的,回家等通知就好了,不用自旋频繁地来打听消息
      if (ws == Node.SIGNAL)
        return true;
      /*
      	如果前继节点的ws值大于0,即为1,说明前继节点处于放弃状态(Cancelled)
      	那就继续往前遍历,直到当前节点的前继节点的ws值为0或负数
      */
      if (ws > 0) {
        do {
          node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
      } else {
        // 将前继节点的ws值设置为Node.SIGNAL,以保证下次自旋时,shouldParkAfterFailedAcquire直接返回true
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
      }
      return false;
    }
    

释放锁

释放锁相当于调用的是AQS中的release()方法,具体内部实现为ReentrantReadWriteLock.Sync.tryRelease()

public final boolean release(int arg) { 
  // arg = 1  
  if (tryRelease(arg)) { 
    Node h = head;
    if (h != null && h.waitStatus != 0)
      // 唤醒后继节点
      unparkSuccessor(h);
    return true;
  }
  return false;
}

protected final boolean tryRelease(int releases) {
  // 解锁线程与当前线程校验
  if (!isHeldExclusively())
    throw new IllegalMonitorStateException();
  // state 字段校验, 写锁是低16位,可以直接做运算
  int nextc = getState() - releases;
  // 如果写锁为0 返回true
  boolean free = exclusiveCount(nextc) == 0;
  if (free)
    setExclusiveOwnerThread(null);
  // 设置state数值
  setState(nextc);
  return free;
}

共享锁

获取锁

读锁的获取调用的是AQS的acquireShared(),实现方式是ReentrantReadWriteLock.Syn.tryAcquireShared()方法

  protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
		// 获取写锁的数量!=0 代表无线程写入   并且  记录上读锁的线程,如果没有读锁,返回null
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
      return -1;
    // 获取读锁的数量
    int r = sharedCount(c);
    // 头节点是否共享
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        // CAS 搞16位+1
        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++;
      }
      // >0 代码执行
      return 1;
    }
    // 自选的方式重新获取
    return fullTryAcquireShared(current);
  }

锁释放

共享锁释放为AQS中的releaseShared(), 具体实现为tryReleaseShared()

        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
          	// 判断当前线程与读锁线程是否同一线程
            if (firstReader == current) {
                // 第一个获取读锁的线程释放锁
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
              	// 其他线程释放
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            // 读锁的释放是可以并发进行的,所以通过CAS来修改state属性值
            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;
            }
        }

posted @ 2022-04-18 22:07  局外人~~  阅读(23)  评论(0编辑  收藏  举报