并发编程12

并发编程12–ReentrantLock解锁流程和读写锁源码

  • {
          
          w  thread=t1}
         ^
         |
         V
    {
          
          r  thread=t2 ws=-1}  <->  {
          
          shared  thread=null prev=null next=null}
    // 比普通独占锁中的节点多了shared属性,下一个节点是shared继续唤醒,直到遇到exclusive
    	 ^
         |
         V
    {
          
          r  thread=t3 ws=-1}  <->  {
          
          shared  thread=null prev=null next=null}
    	 ^
         |
         V
    {
          
          w  thread=t4 ws=-1}  <->  {
          
          exclusive  thread=null prev=null next=null}
         ^
         |
         V
    {
          
          r  thread=t5}
    

写锁的上锁流程

  • package BingFaBianCheng.bingFaBianCheng12;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    
    /**
     * 写锁的上锁流程
     */
    @Slf4j(topic = "enjoy")
    public class RWLock2 {
          
          
    
        //读写锁
        static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
        static Lock r = rwl.readLock();
        static Lock w = rwl.writeLock();
    
    
    
        public static void main(String[] args) throws InterruptedException {
          
          
    
            Thread t1 = new Thread(() -> {
          
          
                w.lock();
    
                try {
          
          
                    log.debug("t1 写锁加锁成功");
                } finally {
          
          
                    w.unlock();
                }
            }, "t1");
    
            t1.start();
            
        }
    
    }
    
    

写锁的上锁流程

  • // 写锁在加锁的时候要么锁没有被人持有则会成功,要么重入成功,其他的都失败
    protected final boolean tryAcquire(int acquires) {
          
          
     	/*
     	 * 1、获取当前线程
     	 */
        Thread current = Thread.currentThread();
        // 获取锁的状态
        int c = getState();
        // 因为读写锁是同一把锁(同一个对象)
        // 为了标识读写锁,把锁的前16位标识读锁的状态
        // 锁的后16位标识写锁的状态
        // 此处是获取写锁的状态
        // int 4字节  32bit
      	// 0000000000000000 0000000000000001
        int w = exclusiveCount(c);
        // 表示有人上了锁(不知道是读锁还是写锁)
        // 1.首先判断锁是否自由
        // 2.因为读写互斥、写写互斥,所以需要判断重入的类型(升级、降级)
        // 2.1降级是允许的
        // 2.2升级是不允许的
        if (c != 0) {
          
          
            // (Note: if c != 0 and w == 0 then shared count != 0)
            // 判断是否重入,同时需要判断重入的类型,所以首先需要知道当前锁的类型
            // 1.w==0表明从来没上过写锁,只能是读锁
            // 而当前自己是来加写锁,所以是锁升级,所以加锁失败
            // 并且此处是写锁加锁,不可能出现锁降级的情况
            // 2.||或  w!=0,说明加过写锁
            // 判断是否是重入,如果不是重入则加锁失败
            if (w == 0 || current != getExclusiveOwnerThread())
                return false;
            // 表示是重入
            // 把后十六位+1 > MAX_COUNT(2^16-1)
            // 但是这个判断条件基本不会true
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            // Reentrant acquire
            // 没有超出重入限制,则把c+1
            setState(c + acquires);
            return true;
        }
        
        // 如果正常情况下就是当前这个例子是第一次加锁
        // 1.writerShouldBlock() 判断自己是否需要排队
        // 如果是非公平锁直接返回false,不排队,直接抢锁
        // 如果是公平锁,返回自己是否需要排队
        // 2.|| !compareAndSetState(c, c + acquires)
        // 公平锁需要排队,则不执行上一句,也就是不去抢锁
        if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
            return false;
        // 加锁成功,把当前持有锁的线程改成自己
        setExclusiveOwnerThread(current);
        return true;
    }
    
  • 公平锁情况,writerShouldBlock()什么时候需要排队?

  • 排队说明队列中有人,但是c又等于0,不是矛盾了吗,其实不是,在高并发情况下,持有锁的线程刚好在代码执行到这里时释放了,因为释放锁的操作又不是原子操作。

  • 在高并发情况下,这些情况是很常见的!

写锁上锁aqs状态

  • Node head  ->   {
          
          thread=null prev=null next=tail waitstatus=-1}
                                ^
                                |
                                V
    Node tail  ->    {
          
          thread=t2 pre=head next=null waitstatus=0} 
    exclusiveOwnerThread:t1
    state:r_w
    // 前十六位标识读锁,后十六位标识写锁
    

写锁解锁流程

  • public final boolean release(int arg) {
          
          
        // 如果解锁成功
        if (tryRelease(arg)) {
          
          
            Node h = head;
            // ws=-1,表示有责任去唤醒下一个节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
  • protected final boolean tryRelease(int releases) {
          
          
        // 判断这把锁是否有人持有
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases;
        // 计算后16位是否等于0
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
            setExclusiveOwnerThread(null);
        // 解锁成功
        setState(nextc);
        return free;
    }
    

读锁

加锁流程

  • package BingFaBianCheng.bingFaBianCheng12;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    
    /**
     * 读锁的上锁流程
     */
    @Slf4j(topic = "enjoy")
    public class RWLock3 {
          
          
    
        //读写锁
        static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
        static Lock r = rwl.readLock();
        static Lock w = rwl.writeLock();
    
    
    
        public static void main(String[] args) throws InterruptedException {
          
          
            
            Thread t1 = new Thread(() -> {
          
          
                r.lock();
    
                try {
          
          
                    log.debug("t1 写锁加锁成功");
                } finally {
          
          
                    r.unlock();
                }
            }, "t1");
    
            t1.start();
    
        }
    
    }
    
    
  • public final void acquireShared(int arg) {
          
          
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    
    // 读锁加锁失败返回-1,成功则返回的数大于0
    protected final int tryAcquireShared(int unused) {
          
          
              
        Thread current = Thread.currentThread();
        int c = getState();
        
        // exclusiveCount(c) != 0拿后十六位,判断是否上了写锁
        // 如果上了写锁,然后再继续判断---重入降级
        // 如果是重入则一定是降级,如果不是重入则失败,因为读写互斥
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return -1;
        
        // 继续往下执行有两种情况
        // 1.没有上写锁  2.重入降级
        int r = sharedCount(c);// 得到上锁的次数
        // 假设恒定不需要排队,重入次数小于最大次数,并且加锁成功
        if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) {
          
          
            // 已经加锁成功了,为什么不直接返回?
            // r是加锁之前的值
            // r==0表明则是第一次给这把锁加读锁
            if (r == 0) {
          
          
                // firstReader就是一个局部变量Thread
                // 如果是第一个线程全局第一次加锁,则把这个线程赋值给firstReader
                firstReader = current;
                // 记录第一个线程的加锁次数
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
          
          
                firstReaderHoldCount++;
            } else {
          
          
                // 相当于一个空的map(记录了线程id,重入次数)
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    // readHolds是threadLocal,初始化HoldCounter,并赋值给rh
                    // 
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            // 加锁成功
            return 1;
        }
        return fullTryAcquireShared(current);
    }
    
    // 读锁是共享锁
    Node head                
    Node tail 
    exclusiveOwnerThread:t1
    state:6_0
    // t1加5次,t2加1次,这里的读锁6次有很多种可能,还可能有更多别的线程
    // aqs中没有办法存储每个线程的重入次数,可以用threadLocal来存储
    
    
    
    

场景1,t1第一次加写锁

  • Node head                           
    Node tail  
    exclusiveOwnerThread:t1
    state:1_0
    firstReader:t1
    firstReaderHoldCount:1
    

场景2,t1重入第二次加锁

  • Node head                           
    Node tail  
    exclusiveOwnerThread:t1
    state:2_0
    firstReader:t1
    firstReaderHoldCount:2
    

场景3,t1释放掉读锁,t2过来拿锁

  • Node head                           
    Node tail  
    exclusiveOwnerThread:t2
    state:1_0
    firstReader:t2
    firstReaderHoldCount:1
    

场景4,t2持有读锁,tn来拿锁,读读并发

  • Node head                           
    Node tail  
    exclusiveOwnerThread:t2
    state:1_0
    firstReader:t2
    firstReaderHoldCount:1
    cachedHoldCounter.count:1
    // 又一个变量,只记录了最后一个线程的重入次数
    // 同时在tn它自己的threadLocalMap中也存了一份
    

场景5,t2持有读锁,tn来拿锁,tm来拿锁,读读并发

  • Node head                           
    Node tail  
    exclusiveOwnerThread:t2
    state:1_0
    firstReader:t2
    firstReaderHoldCount:1
    cachedHoldCounter.count:1
    // tm没来时,记录的是tn的获取锁次数
    // tm来了之后更新为tm的获取锁次数
    #tn_threadLocalMap:1
    // tn的值存在tn的threadLocalMap中
    

读锁加锁失败,去排队

  • // 如果上面加锁失败了
    private void doAcquireShared(int arg) {
          
          
        // new出来的node的标识是shared
        // 如果写成boolean变量就容易懂,这里相当于Node.Node
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
          
          
            boolean interrupted = false;
            for (;;) {
          
          
                final Node p = node.predecessor();
                if (p == head) {
          
          
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
          
          
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
          
          
            if (failed)
                cancelAcquire(node);
        }
    }
    

场景6 t1是写锁,t2和t3是读锁,

  • ws:waitstatus   nw:nextwaiter
    
    Node head ->{thread=null prev=null next=t2 ws=-1}
                                ^
                                |
                                V
                {读锁 thread=t2 pre=head next=t3 ws=-1 nw=SHARED} 
    							^
                                |
                                V
    Node tail ->{读锁 thread=t3 pre=t2 next=null ws=0 nw=SHARED}
    exclusiveOwnerThread:t1(写锁)
    state:2_1
    

解锁流程(此处读锁解锁实际是从LockSupport.lock阻塞的地方继续往下面执行的)

  • private void doAcquireShared(int arg) {
          
          
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
          
          
            boolean interrupted = false;
            for (;;) {
          
          
                final Node p = node.predecessor();
                if (p == head) {
          
          
                    // t2读锁阻塞被唤醒后获得锁
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
          
          
                        // 继续往后传播,读锁持续获得锁
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
          
          
            if (failed)
                cancelAcquire(node);
        }
    }
    
  • private void setHeadAndPropagate(Node node, int propagate) {
          
          
        Node h = head; // Record old head for check below
        // 把自己设置成头结点
        setHead(node);
        
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
          
          
            // 取出t3
            Node s = node.next;
            if (s == null || s.isShared())
                // 把t3叫醒,依然是LockSupport.lock阻塞的地方继续往下面执行
                doReleaseShared();
        }
    }
    
  • private void doReleaseShared() {
          
          
        
        for (;;) {
          
          
            Node h = head;
            if (h != null && h != tail) {
          
          
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
          
          
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
    

读读并发原理

  • 加锁的过程>0,依然可以加锁成功
  • 如果唤醒的是一把读锁,会循环往下面判断是否是共享锁(nextwaiter==SHARED),直到遇到的不是读锁

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/111189597
今日推荐