Java并发编程 --- AQS再续前缘

在同步机制中,我们介绍了AQS与ReentrantLock。当然使用AQS进行同步的也不止ReentrantLock,所以我们接下来去看看其他用AQS做同步的类。

ReentrantReadWriteLock

概述

ReentrantReadWriteLock的读锁是共享锁写锁是独占锁

使用时,读-读支持并发,读-写互斥,写-写互斥。

重入时升级不支持:如果一个线程获取了读锁,然后想再获取写锁,那是不被允许的。

重入时降级支持:如果一个线程获取了写锁,允许再持有读锁。

ReadLock

lock()

        public void lock() {
            sync.acquireShared(1);
        }

ReadLock的lock调用了同步器的acquireShared(1)方法。

    public final void acquireShared(int arg) {
        // tryAcquireShared 返回负数, 表示获取读锁失败
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

进入同步器的方法,其中tryAcquireShared(arg)是尝试去获取锁,其返回值-1表示失败,0表示成功,但后继节点不会继续唤醒。正数表示成功,而且数值是后面还有几个后继节点需要唤醒,读写锁返回 1。



     protected final int tryAcquireShared(int unused) {
            //获得当前线程
            Thread current = Thread.currentThread();
            int c = getState();
            // exclusiveCount(c) 代表低 16 位, 写锁的 state,成立说明有线程持有写锁
            // 写锁的持有者不是当前线程,则获取读锁失败,【写锁允许降级】
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            // 读锁是否应该阻塞
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                // 尝试增加读锁计数
                compareAndSetState(c, c + SHARED_UNIT)) {
                //加锁之前发现读锁为0 说明没人用读锁
                if (r == 0) {
                    //指明当前线程为第一个Reader
                    firstReader = current;
                    //firstReaderHoldCount为第一个Reader的入锁次数
                    firstReaderHoldCount = 1;
                //发生的读锁的重入  
                } else if (firstReader == current) {
                    //第一个Reader的入锁次数+1
                    firstReaderHoldCount++;
                } else {
                    // cachedHoldCounter 设置为当前线程的 holdCounter 对象,即最后一个获取读锁的线程
                    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;
            }
            // 逻辑到这应该阻塞,或者 cas 加锁失败
            // 会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
            return fullTryAcquireShared(current);
        }

        final int fullTryAcquireShared(Thread current) {
            // 当前读锁线程持有的读锁次数对象
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                // 说明有线程持有写锁 持有写锁的线程可以进一步获取读锁
                if (exclusiveCount(c) != 0) {
                    //如果持有写锁的不是当前线程
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                //读锁是否应该阻塞
                } else if (readerShouldBlock()) {
                    //第一个持有读锁线程是否是当前线程,条件成立说明当前线程是 firstReader,当前锁是读忙碌状态,而且当前线程也是读锁重入
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            // 最后一个读锁的 HoldCounter
                            rh = cachedHoldCounter;
                            // 说明当前线程也不是最后一个读锁
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                //溢出会抛出异常
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // CAS让读数据+1
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        //指明当前线程为第一个Reader
                        firstReader = current;
                        //初始化第一个Reader的入锁次数
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        //第一个Reader的入锁次数+1
                        firstReaderHoldCount++;
                    } else {
                        //cachedHoldCounter为成功获取 readLock 的最后一个线程的保持计数
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        //更新计数器
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

上述为tryAcquireShared的业务代码,需要判断的有:

1、是否为锁重入,为锁重入可以获取读锁。(因为不管是当前线程持有写锁还是读锁,都能再次拿到读锁)

2、是否有人获取了写锁,若获取写锁的线程可以成功获取读锁。当遇到阻塞,或者 cas 加锁失败就会执行fullTryAcquireShared的业务逻辑了。

     private void doAcquireShared(int arg) {
        //将当前节点添加到Node队列,同时设置Node的状态为SHARED
        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;
                    }
                }
                // 是否在获取读锁失败时阻塞
                // shouldParkAfterFailedAcquire(p, node)将node的前驱节点的WaitStatus设置为-1,代表它需要唤醒它的后继节点。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //parkAndCheckInterrupt()将当前线程park阻塞住
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

获取读锁失败,进入 sync.doAcquireShared(1) 流程开始阻塞,首先也是调用 addWaiter 添加节点不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时线程 仍处于活跃状态。然后进入for()循环块中,再次使用tryAcquireShared(arg)尝试获取锁,若锁获取失败,则调用shouldParkAfterFailedAcquire(p, node)将node的前驱节点的WaitStatus设置为-1,代表它需要唤醒它的后继节点。

当有人唤醒阻塞线程会再进入for一次,再次使用tryAcquireShared(arg)尝试获取锁,若还是没有获取成功,parkAndCheckInterrupt()将当前线程park阻塞住。

unlock()

public void unlock() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    //尝试释放锁
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

unlock的核心逻辑为同步器的releaseShared方法。

      protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            //判断第一个获取读锁的是不是当前线程
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                //获取最后一个获取写锁的人
                HoldCounter rh = cachedHoldCounter;
                //rh == null 表示无人获取
                //rh.tid != getThreadId(current) 表示当前线程不是获取写锁的线程
                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直到把读锁的状态量置为0
            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;
            }
        }

如果释放锁成功之后,那就执行doReleaseShared();去释放它的后继节点,如果后继节点也为Shared则能成功释放。

    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //判断waitStatus是否为-1 若为-1 则证明要唤醒后继节点
                if (ws == Node.SIGNAL) {
                    //使用cas将waitStatus从-1变成0
                    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;
        }
    }
   //执行unpark后继节点 
   private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        //获取当前节点的waitstatus
        int ws = node.waitStatus;
        //若小于0 则说明有职责唤醒后继节点
        if (ws < 0)
            //CAS 将waitstatus置为0
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        //获取下一个节点
        Node s = node.next;
        //若下一个节点不存在或者被取消 则需要找一个重新唤醒的对象
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从后往前遍历
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //s不为null 开始唤醒
        if (s != null)
            //本质由Unsafe提供unpark方法
            LockSupport.unpark(s.thread);
    }

WriteLock

lock()

        public void lock() {
            sync.acquire(1);
        }

        public final void acquire(int arg) {
           //tryAcquire(arg)是尝试去获取锁
           if (!tryAcquire(arg) &&
              acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
              selfInterrupt();
        }

lock()主要使用了同步器的acquire(1)方法。

        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)
                //w == 0表明不是获取的写锁,那肯定是获取了读锁,获取读锁不能再获取写锁(无法锁升级)
                //w != 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;
            }
            // c == 0,说明没有任何锁,判断写锁是否该阻塞,是 false 就尝试获取锁,失败返回 false
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            //设置锁主人
            setExclusiveOwnerThread(current);
            return true;
        }

上述是tryAcquire方法,它讨论了两种情况:

1、c≠0,那么就有可能有线程获取了读锁或者写锁,若有线程获取了读锁,那其他线程则无法获取写锁(读-写互斥);若有线程获取了写锁,那么要看写锁的拥有者是否是当前线程,若是,则相当于锁重入,若不是,则无法获取写锁。

2、c=0,说明没有任何锁,判断写锁是否该阻塞,是 false 就尝试获取锁,失败返回 false

     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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

acquireQueued是将当前节点加到Node队列中,且Node的状态为EXCLUSIVE

unlock()

public void unlock() {
    // 释放锁
    sync.release(1);
}
public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        // 头节点不为空并且不是等待状态不是 0,唤醒后继的非取消节点
        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;
    // 因为可重入的原因, 写锁计数为 0, 才算释放成功
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        //证明当前的后继节点需要唤醒
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        //下一个节点为空或者节点取消
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

StampedLock

概述

StampedLock与ReentrantLock不同的点在于,StampedLock提供了乐观读机制。

乐观读机制

StampedLock的乐观读机制,类似于无锁操作,完全不会阻塞写线程获取写锁。

作用:可以缓解读多写少时写线程的"饥饿"现象。

适用场景

适用于读多写少的场景,可以避免写线程饥饿。

DEMO & TIPS

由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题,所以当使用StampedLock的乐观读锁时需要使用以下流程:

1、先使用tryOptimisticRead获取时间戳(如果有写锁,直接返回0)

2、使用validate去检验锁状态,判断数据是否一致

class StampedLockTest{
    private int data;
    private final StampedLock lock = new StampedLock();

    public StampedLockTest(int data){
        this.data = data;
    }
    /**
    * 传入的readTime为模拟获取乐观读时间戳后所需要的执行业务时间。
    */
    public int read(int readTime){
        //乐观读
        long stamp = lock.tryOptimisticRead();
        System.out.println("stamp = " + stamp);
        try {
            Thread.sleep(readTime);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //检验戳
        if(lock.validate(stamp)){
            System.out.println("乐观读成功");
            return data;
        }
        System.out.println("乐观读失败,进行加锁");
        try {
            stamp = lock.readLock();

            System.out.println("读取结果结束");
            return data;
        }finally {
            lock.unlockRead(stamp);
        }
    }

    public void write(int data){
        //获取写锁
        long stamp = lock.writeLock();
        System.out.println("stamp = " + stamp);
        try {
            this.data = data;
            System.out.println("修改数据成功");
        }finally {
            lock.unlockWrite(stamp);
        }
    }
}

总结

乐观读操作的步骤如下:

①先使用tryOptimisticRead()获取时间戳stamp

②使用validate(long stamp)去验证时间戳是否失效。

失效场景:有其他线程获取了写锁

③若失效,则进一步去获取读锁。

原理

锁状态

StampedLock提供了写锁、悲观读锁、乐观锁三种模式。

悲观读锁:state的前7位(0-7位)表示获取读锁的线程数。如果超过0-7位最大容量 255,则使用一个名为 readerOverflow 的 int 整型保存超出数。

所以在加写锁时,只需要 state & 255,若结果大于0,则表明有线程数持有读锁。结果为0,则可以直接加写锁。(当然还需要CAS竞争写锁)

tryOptimisticRead()

tips:

①private static final long WBIT = 1L << LG_READERS = 128

②private static final long RBITS = WBIT - 1L = 127

③private static final long SBITS = ~RBITS = -128

public long tryOptimisticRead() {
     long s;
     // 若已有写锁获取 直接返回0L
     return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}

validate(long stamp)

public boolean validate(long stamp) {
     //内存屏障
     U.loadFence();
     return (stamp & SBITS) == (state & SBITS);
}

使用了Unsafe提供的loadFence(),主要起到了内存屏障的作用,它确保在该方法调用之后的读操作不会被重排序到该方法调用之前这有助于保证多线程环境下,对共享变量的读取操作能够看到其他线程对该变量的最新写入结果。

ReadLock

lock()

public void lock() { 
  readLock(); 
}

public long readLock() {
    long s = state, next;  
    // 当whead == wtail时 表示等待队列现在没有人 可获取锁
    return ((whead == wtail && (s & ABITS) < RFULL &&
          U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
          next : acquireRead(false, 0L));
 }

unlock()

public void unlock() { unstampedUnlockRead(); }

final void unstampedUnlockRead() {
     for (;;) {
         long s, m; WNode h;
         if ((m = (s = state) & ABITS) == 0L || m >= WBIT)
             throw new IllegalMonitorStateException();
         else if (m < RFULL) {
            // CAS将state-- 
            if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) {
                
               if (m == RUNIT && (h = whead) != null && h.status != 0)
                    //唤醒 h 的后继者(通常为 whead)。这通常只是 h.next,但如果 next 指针滞后,则可能需要从 wtail 遍历。 
                    release(h);
                 break;
             }
         }
         // 尝试递减 readerOverflow
         else if (tryDecReaderOverflow(s) != 0L)
                break;
     }
}

WriteLock

lock()

public void lock() { writeLock(); }

public long writeLock() {
     long s, next; 
     // 先判断锁是否被读锁阻塞
     return ((((s = state) & ABITS) == 0L &&
           // 使用CAS进行state的写资源++
           U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
           next : acquireWrite(false, 0L));
}

Semaphore

概况

Semaphore又名为信号量。使用时,需要指定该信号量有多少通行证(permits),通行证表明了有多少资源数可以被线程获取。

原理

acquire()

该方法的目的是为了获取通行证,本质是通过CAS获取。

//默认获取一个通行证
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
//获取指定的通行证
public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    //线程打断直接停止
    if (Thread.interrupted())
        throw new InterruptedException();
    //尝试获取通行证(核心) 大于等于0表示获取成功 小于0表示获取失败
    if (tryAcquireShared(arg) < 0)
        //获取通行证失败 需要进行相应的阻塞
        doAcquireSharedInterruptibly(arg);
}

//核心(公平版本)
protected int tryAcquireShared(int acquires) {
     for (;;) {
         //公平版本比非公平版本多了这个判断条件
         //当发现队列中有人时就直接默认获取失败
         if (hasQueuedPredecessors())
               return -1;
         //获取state
         int available = getState();
         //预期结果
         int remaining = available - acquires;
         //如果预期结果小于0 那么肯定就是没有资源了 所以直接退出
         if (remaining < 0 ||
             // 通过CAS更新资源(由Unsafe鼎力支持)
             compareAndSetState(available, remaining))
             return remaining;
     }
}
//CAS
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

// 作用:①如果是队列中的头节点,会做最后的挣扎 ②如果没有会进行park ③允许等待过程中打断
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //为当前的线程创建节点并将其放入指定的队列中
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
       for (;;) {
           //返回node的前驱节点
           final Node p = node.predecessor();
           //如果相等 表示它是等待队列中第一个元素
           if (p == head) {
               //继续进行CAS资源的获取 (垂死挣扎)
               int r = tryAcquireShared(arg);
               //表示获取成功
               if (r >= 0) {
                        //设置队列头,并检查后续任务是否可以在共享模式下等待 (如下细讲)
                        setHeadAndPropagate(node, r);
                        //将最开始的队列头置为null 便于GC进行垃圾回收 不然会被强制绑定 无法GC
                        p.next = null; // help GC
                        failed = false;
                        return;
               }
           }
           //如果执行到这里 就意味着 线程需要进行Park操作了
           if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
      }
   } 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) {
            Node s = node.next;
            if (s == null || s.isShared())
                //唤醒后继共享节点
                doReleaseShared();
        }
}

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            // 如果head为null 或者 head == tail(表明队列中无人) 则直接结束
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // ws==Node.SIGNAL表示当前节点有义务唤醒后继节点
                if (ws == Node.SIGNAL) {
                    // 用CAS将waitstatus变为0
                    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;
        }
}

//唤醒后继节点逻辑
private void unparkSuccessor(Node node) {
        //再上一层保险 保证它肯定waitstatus变为0再执行唤醒后继节点操作        
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        
        Node s = node.next;
        //满足条件即说明 node.next是一个已经取消或不存在的点 所以需要我们重新去查找
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从后往前 去查找非取消节点 (为什么这么做,楼下细说)
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
}

在unparkSuccessor中,为什么从后往前遍历寻找未被取消的节点呢呢?

在我们阅读发现一个有趣的现象,那就是为什么要从后往前遍历。

首先先看addWaiter()

private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);
	// Try the fast path of enq; backup to full enq on failure
    //获取尾部Node
	Node pred = tail;
	if (pred != null) {
		//绑定老尾部节点
        node.prev = pred;
        //CAS去设置自己为尾部节点
		if (compareAndSetTail(pred, node)) {
			pred.next = node;
			return node;
		}
	}
	enq(node);
	return node;
}

以图绘制发来说明情况

image.png

存在情况如下:如果刚好没执行pred.next = node;然后此时需要从前往后找去唤醒,那么我们其实的唤醒的是新节点,但是由于他们还没建立正向通道,所以遍历不到新节点。

当然,还有另外一个原因,在节点取消设置为CANCEL时,会先断掉向前的指针,造成遍历不到最新的节点。

release()

其实严格来说,他的主要作用是补充通行证,他不需要你持有通行证才能补充,只要你想随时补充。(Tips:会有一个溢出的问题)

public void release() {
   sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    //执行补充许可证的主要作用(溢出会抛出异常)
    if (tryReleaseShared(arg)) {
       //唤醒等待链中的线程
       doReleaseShared();
       return true;
    }
    return false;
}

//执行补充许可证的操作(用CAS保证数据安全)
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
       int current = getState();
       int next = current + releases;
       //可能出现溢出
       if (next < current) // overflow
           throw new Error("Maximum permit count exceeded");
       //通过CAS去补充通行证,保证线程安全
       if (compareAndSetState(current, next))
           return true;
    }
}

//执行唤醒后继节点的操作
private void doReleaseShared() {
    for (;;) {
       Node h = head;
       if (h != null && h != tail) {
           int ws = h.waitStatus;
           //判断ws是否为-1 如果是-1就要唤醒后继节点
           if (ws == Node.SIGNAL) {
               //用cas将waitStatus从-1置为0
               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;
}

CountDownLatch

概述

又名为计数器,用来进行线程同步,等待所有线程完成

方法

public CountDownLatch(int count) : 初始化需要coutDown几次才能唤醒所有线程。

public void await() : 让当前线程等待,必须 down 完初始化的数字才可以被唤醒,否则进入无限等待

public void countDown() : 计数器进行减 1(down 1)

DEMO

public class test4 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        ExecutorService service = Executors.newFixedThreadPool(10);
        Random random = new Random();
        String[] str = new String[10];
        for (int i = 0; i < 10; i++) {
            int k = i;
            service.execute(() -> {
                for (int j = 0; j <= 100; j++) {
                    str[k] = j+"%";
                    try {
                        Thread.sleep(random.nextInt(100));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.print("\r"+ Arrays.toString(str));
                }
                //减一
                countDownLatch.countDown();
            });

        }
        //等待count减到0
        countDownLatch.await();
        System.out.println("\n"+"等待结束");
    }
}

原理

await()

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    // 提供可打断机制
    if (Thread.interrupted())
         throw new InterruptedException();
    // 尝试去释放
    if (tryAcquireShared(arg) < 0)
         // 小于0,如要加入等待队列中 
         doAcquireSharedInterruptibly(arg);
}

// 释放机制
protected int tryAcquireShared(int acquires) {
    // 若state为0,表示可释放;若不为0,表示不可释放。
    return (getState() == 0) ? 1 : -1;
}

//
private void doAcquireSharedInterruptibly(int arg)
      throws InterruptedException {
      final Node node = addWaiter(Node.SHARED);
      boolean failed = true;
      try {
          for (;;) {
              final Node p = node.predecessor();
              if (p == head) {
                  int r = tryAcquireShared(arg);
                  if (r >= 0) {
                      setHeadAndPropagate(node, r);
                      p.next = null; // help GC
                      failed = false;
                      return;
                  }
              }
              if (shouldParkAfterFailedAcquire(p, node) &&
                  parkAndCheckInterrupt())
                  throw new InterruptedException();
          }
      } finally {
          if (failed)
              cancelAcquire(node);
      }
}

countDown()

public void countDown() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    // 尝试state-1
    if (tryReleaseShared(arg)) {
         // 看唤醒后续的节点去减1
         doReleaseShared();
         return true;
    }
    return false;
}

// CAS去循环尝试将state-1 
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
         int c = getState();
         if (c == 0)
              return false;
         int nextc = c-1;
         if (compareAndSetState(c, nextc))
              return nextc == 0;
    }
}

//唤醒后继节点
private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        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;
        }
}
posted @   ayu0v0  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示