源码分析AbstractQuenedSynchronized(三)

第一篇:源码分析AbstractQuenedSynchronized(一)
第二篇:源码分析AbstractQuenedSynchronized(二)

本文主要结合AQS分析JDK并发包中的几个非常有用的并发工具类:CountDownLatch、CyclicBarrier和Semaphore.


CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。
其内部依赖一个AQS的实现类Sync实现功能,如下图所示
在这里插入图片描述

使用示例

import java.util.concurrent.CountDownLatch;

public class CountDownLatch_Test {
    static CountDownLatch t3= new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        Thread work1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("任务1处理结束");
                t3.countDown();
            }
        });
        Thread work2 = new Thread(new Runnable() {
            public void run() {
                System.out.println("任务2处理结束");
                t3.countDown();
            }
        });
        work1.start();
        work2.start();
        t3.await();//阻塞,直到count减为0
        System.out.println("两个任务处理结束!");
    }
}

在这里插入图片描述
图片源于https://javadoop.com/post/AbstractQueuedSynchronizer-3

构造函数

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);//count实际被设置为同步状态的state值
    }
    
    private final Sync sync;
    
   /**
     * 使用内部类Sync来实现功能
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        //构造函数:将state设置为count值
        Sync(int count) {
            setState(count);
        }
        
        //该方法用于判断state是否为0
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
		
		//用在countDown方法中,将state减1,然后判断state是否为0
        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;
            }
        }
    }

CountDownLatch的重点其实就是await和countDown 两个方法,我们先分析await方法,看看线程是如何被挂起的

await方法(进入该方法到线程挂起)

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    /*====================AQS:acquireSharedInterruptibly==============================
       public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //检查到线程中断,则直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        //state!=0,则直接从await方法返回,否则进入doAcquireSharedInterruptibly方法
        if (tryAcquireShared(arg) < 0)//return (state == 0) ? 1 : -1;
            doAcquireSharedInterruptibly(arg);
    }   
    =====================================================================================*/

    /*====================AQS:doAcquireSharedInterruptibly==========================
        private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
        //当前线程node加入阻塞队列末尾,状态为SHARED,由此我们看出调用await方法的线程一开始都会被加入阻塞队列中
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    //只要state不等于0,这个值返回-1
                    int r = tryAcquireShared(arg);//return (state == 0) ? 1 : -1;
                    //第一次进入await方法时,r应该是为-1的,这个if代码块主要是线程被唤醒后的后续处理过程,等线程被唤醒后我们再回头看里面的逻辑
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //调用await方法,正常情况下第一次走这里的逻辑,在这里面线程会被挂起,等待唤醒
                if (shouldParkAfterFailedAcquire(p, node) &&//该方法为AQS方法,主要将node的前驱结点p设为SINGNAL状态
                    parkAndCheckInterrupt())//该方法为AQS方法,利用LockSupport工具挂起当前线程,同时返回线程的中断状态
                    throw new InterruptedException();//如果进入这个逻辑,则说明线程中断了,抛出中断异常
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
     =======================================================================================*/

在shouldParkAfterFailedAcquire方法中线程被挂起后,我们接下来就可以看线程是如何被唤醒的,即分析countDown方法


countDown方法

    public void countDown() {
        sync.releaseShared(1);
    }
    
    /*=============AQS的releaseShared方法=========================
        public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//将state减1,然后判断state是否为0
        	//state为0,准备唤醒线程
            doReleaseShared();
            return true;
        }
        //state不为0,直接返回
        return false;
    }
    ================================================================== */

    /*=============AQS的doReleaseShared方法,state==0时才会进入该方法=========================
        private void doReleaseShared() {
        for (;;) {
            Node h = head;
            //h为null说明阻塞队列为空,h=tail说明head后面没有结点在排队,即阻塞队列的结点都被唤醒了,因此这两种情况不需要继续唤醒后继结点了
            if (h != null && h != tail) {
                int ws = h.waitStatus;//还记得分析await时的shouldParkAfterFailedAcquire方法吗?
                //等待线程t3入队的时候已经将它的前继结点的waitStatus设为SINGAL(-1)了,所以head的状态为SINGAL
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // CAS失败则重新循环
                    unparkSuccessor(h);//唤醒head的后继结点t3
                }
                else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            
            if (h == head)                   // loop if head changed
                break;
        }
    }
    ================================================================== */

await方法(state==0后被唤醒)

//回顾await方法(进入该方法到线程挂起),调用await方法的线程被唤醒后会进入这个if逻辑
      if (r >= 0) {
 			setHeadAndPropagate(node, r);
   			p.next = null; // help GC
            failed = false;
            return;
  }

    /*=============AQS的setHeadAndPropagate方法,node为head的后继结点,propagate=1=========================
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        //将即将要被唤醒的结点(head的后继结点)设为头结点
        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方法唤醒队列中后面的结点
            	//调用doReleaseShared方法大概的过程是这样的:state减为0后,
            	//第一次从countdown方法中调用该方法会唤醒阻塞队列中的第一个结点,唤醒后的结点还会继续调用该方法去唤醒后面的结点
                doReleaseShared();
        }
    }
    ================================================================== */

    /*=============AQS的doReleaseShared方法,state==0时才会进入该方法=========================
        private void doReleaseShared() {
        for (;;) {
            Node h = head;
            //h为null说明阻塞队列为空,h=tail说明head后面没有结点在排队,即阻塞队列的结点都被唤醒了,因此这两种情况不需要继续唤醒后继结点了
            if (h != null && h != tail) {
                int ws = h.waitStatus;//还记得分析await时的shouldParkAfterFailedAcquire方法吗?
                //等待线程t3入队的时候已经将它的前继结点的waitStatus设为SINGAL(-1)了,所以head的状态为SINGAL
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // 循环直到将head状态设为0
                    unparkSuccessor(h);//唤醒head的后继结点t3
                }
                else if (ws == 0 &&//这个CAS失败的场景是:执行到这里,刚好有一个结点入队,入队会将这个ws设为-1
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            
            if (h == head)                  
            //这是多次调用doReleaseShared方法后最终结束的地方,当最后一次调用doReleaseShared进入到这里时说明阻塞队列的所有结点都被唤醒,head后面没有结点了
                break;
        }
    }
    ================================================================== */

CyclicBarrier

CyclicBarrier的字母意思是可循环使用的屏障。它的功能是:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,打开屏障,所有在栅栏上的线程才会继续运行。
在这里插入图片描述
图片源于https://javadoop.com/post/AbstractQueuedSynchronizer-3

package java.util.concurrent;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class CyclicBarrier {
    //CyclicBarrier与CountDownLatch相比可以重复使用,因此具有代的概念
    private static class Generation {
        boolean broken = false;
    }

    /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    //屏障拦截的线程数量
    private final int parties ;
    //越过屏幕前优先执行的动作
    private final Runnable barrierCommand;
    //当前所属的代
    private Generation generation = new Generation();

    //还没到达屏障的线程,从parties递减到0
    private int count;

    /**
     * 开启新的一代
     */
    private void nextGeneration() {
        // 唤醒在栅栏上等待的所有线程
        trip.signalAll();
        // 建立新的一代
        count = parties;
        generation = new Generation();
    }

    /**
     * 打破栅栏
     */
    private void breakBarrier() {
        //设置标志位
        generation.broken = true;
        //重置count,唤醒栅栏上所有等待的线程
        count = parties;
        trip.signalAll();
    }

    /**
     * Main barrier code, covering the various policies.
     */
    private int dowait(boolean timed, long nanos)//timed:是否带有超时机制
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        //该方法中会执行Condition的await方法,因此首先肯定要获得锁
        lock.lock();
        try {
            final Generation g = generation;
            //栅栏被打破,抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
            //线程中断,抛出异常
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;//调用一次await方法,count减1
            //count==0,准备让栅栏上线程通过
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    //如果设置了barrierCommand则优先执行barrierCommand
                    if (command != null)
                        command.run();
                    ranAction = true;//设置标志位,说明command.run()没有发生异常
                    nextGeneration();//最后一个线程到达屏障后会唤醒所有线程并建立新的一代
                    return 0;
                } finally {
                    if (!ranAction)
                        //如果执行指定操作的时候发生了异常,则需要打破栅栏
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)
                        //没有超时机制
                        trip.await();//释放锁
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    //逻辑都这里,等待的线程在Condition的await方法中被中断
                    if (g == generation && ! g.broken) {
                        //仍然在当前代,之前栅栏也没有被打破过
                        //则,打破栅栏并抛出异常
                        breakBarrier();
                        throw ie;
                    } else {
                        //逻辑走到这里,说明新的一代已经产生了,即最后一个线程await执行完成,因此没有必要抛出中断异常,记录中断信息即可
                        Thread.currentThread().interrupt();
                    }
                }

                //醒来发现栅栏已经被打破,抛出异常
                if (g.broken)
                    throw new BrokenBarrierException();

                // 这个 for 循环除了异常,就是要从这里退出了
                // 我们要清楚,最后一个线程在执行完指定任务(如果有的话),会调用 nextGeneration 来开启一个新的代
                // 然后释放掉锁,其他线程从 Condition 的 await 方法中得到锁并返回,然后到这里的时候,其实就会满足 g != generation 的
                // 那什么时候不满足呢?barrierCommand 执行过程中抛出了异常,那么会执行打破栅栏操作,
                // 设置 broken 为true,然后唤醒这些线程。这些线程会从上面的 if (g.broken) 这个分支抛 BrokenBarrierException 异常返回
                // 当然,还有最后一种可能,那就是 await 超时,此种情况不会从上面的 if 分支异常返回,也不会从这里返回,会执行后面的代码
                if (g != generation)
                    return index;

                //醒来发现超时,打破栅栏,抛出异常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;//count初始为parties
        this.barrierCommand = barrierAction;
    }

    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    public int getParties() {
        return parties;
    }

    //不带超时机制
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

    //带超时机制,会抛出异常
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }


    //直接通过broken的值来判断栅栏是否被打破
    /**
     * 有以下几种情况,栅栏会被打破
     * 1.线程中断
     * 2.超时
     * 3.执行指定barrierCommand操作时发生了异常
     */
    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }

    public void reset() {
        final ReentrantLock lock = this.lock;
        //打破栅栏的过程中有一个步骤是唤醒等待线程,而signalAll操作是需要获得锁的,因此会进行加锁操作
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

    //获得在栅栏上等待的线程数
    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
        }
    }
}

Semaphore

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程以保证合理的使用公共资源。

使用示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreTest {
    private static final int THREAD_COUNT=30;
    private static ExecutorService threadPool= Executors.newFixedThreadPool(THREAD_COUNT);
    private static Semaphore semaphore=new Semaphore(10);//只允许10个线程同时并发

    public static void main(String[] args) {
        for (int i = 0; i <THREAD_COUNT; i++) {
            threadPool.execute(new Runnable() {
                public void run() {
                    try {
                        semaphore.acquire();//获取许可证
                        System.out.println("save data");
                        semaphore.release();//归还许可证
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        threadPool.shutdown();
    }
}

在这里插入图片描述
Semaphore的内部结构和ReentrantLock很相似,内部都有一个AQS的实现类Sync,Sync又有两个实现类,用于实现两种公平策略。

构造函数

    //默认非公平锁
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    //可通过构造参数指定公平策略
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

acquire方法

//下面四种方法主要区分在获取指定数量的资源以及是否响应中断

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public void acquireUninterruptibly() {
    sync.acquireShared(1);
}
public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}
public void acquireUninterruptibly(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireShared(permits);
}

首先来看我们常用的acquire()方法,它的功能是响应中断式的获取一个资源(许可证)

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

    /*=============AQS类的acquireSharedInterruptibly方法============
        public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //检查到线程中断,则直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
        //当state已经为0时即资源为空时,remaining<0,此时线程准备进入阻塞队列
            doAcquireSharedInterruptibly(arg);
    }
    ==================================================================*/

由于Sync有两个实现类,对应两种公平策略,它们都重写了tryAcquireShared(arg)方法,下面进行对比:

//==================非公平策略NonfairSync=======================
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
        
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
//=================公平策略FairSync============================
        protected int tryAcquireShared(int acquires) {
            for (;;) {
                //和非公平策略相比区别:首先会判断是否有线程在阻塞队列中排队,即head结点后面有结点在排队
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

当state为0后,再调用acquire方法就会进入下面的逻辑,线程加入到阻塞队列中,这个方法在CountdownLatch中介绍过,这里就不赘述了

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //当前线程node加入阻塞队列末尾
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    //只要state不等于0,这个值返回-1
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //parkAndCheckInterrupt方法中线程挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

线程挂起后要等待其他线程调用realease方法释放资源,才能重新获得资源继续执行,下面看release方法

release方法

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

    public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }

/*===================AQS:releaseShared=======================
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
=================================================================*/

		//方法很简单,释放资源,state增加
        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");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

/*这个方法在Countdownlatch中也介绍过,主要作用是唤醒阻塞队列中的结点
===================AQS:doReleaseShared=============================
    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            //h为null说明阻塞队列为空,h=tail说明阻塞队列的结点都被唤醒了,因此这两种情况不需要继续唤醒后继结点了
            if (h != null && h != tail) {
                int ws = h.waitStatus;//head结点后面的结点在入队的时候已经将头结点的waitStatus设为SINGAL(-1)了
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // 循环直到将head状态设为0
                    unparkSuccessor(h);//唤醒head的后继结点
                }
                else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //说明
            if (h == head)                   // loop if head changed
                break;
        }
    }
====================================================================*/

在搞懂CountdownLatch的基础上,分析Semaphore十分轻松。


总结

  1. CountdownLatch和Semaphore的实现思路很相似,内部都有一个AQS的实现类Sync,都是将线程加入到阻塞队列中挂起,前者是多个线程调用countdown方法用于state递减,state减为0之前调用await方法的线程都加入到阻塞队列中挂起,当state减为0后阻塞队列中的线程被唤醒;后者是线程调用acquire方法将state递减,state减为0之前这些线程都能获得资源,state减为0后将线程都添加到阻塞队列中挂起,调用release方法增加state使其大于0后尝试去唤醒阻塞队列中的线程。
  2. CyclicBarrier主要通过Condition的实现类ConditionObject来实现,内部有个类Generation用来维护当前代的栅栏是否已被打破,在count(还没有达到屏障的线程数)减为0之前达到线程的屏障都会调用condition的await方法加入到条件队列中挂起等待,当count==0后,说明所有线程都已达到屏障,这时候就会释放屏障,唤醒所有等待队列中的结点,它们转移到阻塞队列中重新尝试去获得锁继续执行

参考:https://javadoop.com/post/AbstractQueuedSynchronizer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值