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

  • 相关阅读:
    8626 原子量计数
    17229 Lry,你除了2还是2
    11153 kill boss
    1143 多少个Fibonacci数
    8614 素数__
    We Chall-Training: Stegano I-Writeup
    We Chall-Training: Get Sourced-Writeup
    We Chall-Prime Factory-Writeup
    CTF入门指南
    pwnable.kr-collision -Writeup
  • 原文地址:https://www.cnblogs.com/Tonyzczc/p/16163094.html
Copyright © 2020-2023  润新知