CountDownLatch、CyclicBarrier和 Semaphore

CountDownLatch、CyclicBarrier和 Semaphore

简介

CountDownLatch、CyclicBarrier和 Semaphore是J.U.C中的三个辅助类,大致功能如下:

CountDownLatch可以实现线程A等待其他多个线程执行完后再继续执行。

CyclicBarrier可以实现一组线程等待至某个状态后再全部同时执行。

Semaphore可以控同时访问的线程个数,比如3个资源有5个线程请求,则只有3个线程能得到。

借助上面三个辅助类能完成很多并发的问题。详细介绍之前,需要了解下volatile关键字、CAS操作和AQS,这里只是简单介绍下。

volatile关键字

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。也就是保证变量的值是最新的。

CAS

CAS(Compare and Swap)是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试(自旋,使用返回的最新值再次计算进行CAS操作)。

AQS

AbstractQueuedSynchronizer抽象队列同步器,是用来构建锁或其他同步组件的基础框架。它使用了一个int成员变量表示同步状态,通过内置FIFO队列来完成资源获取线程的排队工作。AQS提供三个(原子式)方法(getState()、setState(int newState)、compareAndSetState(int expect,intupdate)[使用CAS(CompareAndSwap)设置当前状态,保证方法的原子性])来保证状态的改变是安全的。

CountDownLatch

java.util.concurrent包下的一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

例子

public class test_CountDownLatch {
    public static void main(String[] args){
        final CountDownLatch latch = new CountDownLatch(3);
        for (int i=0;i<3;i++){
            new Thread(new worker(latch)).start();
        }
        try {
            System.out.println("等待子线程执行完毕...");
            latch.await();
            System.out.println("3个子线程已经执行完毕");
            System.out.println("继续执行主线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class worker implements Runnable{
        private final CountDownLatch latch;
        public worker(CountDownLatch latch){
            this.latch=latch;
        }
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName()+"正在工作");
                Thread.sleep(2048);
                System.out.println(Thread.currentThread().getName()+"工作完毕");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出:从结果可以看出0,1,2是并行执行,然后main线程等待所有结束后才继续执行。

等待子线程执行完毕...

Thread-1正在工作

Thread-0正在工作

Thread-2正在工作

Thread-0工作完毕

Thread-1工作完毕

Thread-2工作完毕

3个子线程已经执行完毕

继续执行主线程

源码

静态内部类

静态内部类的同步器Sync,继承AQS,就是控制并发环境下对线程数量的原子性操作。

/**
 * Synchronization control ForCountDownLatch.
 * Uses AQS state to represent count.
 */
private staticfinal class Sync extends AbstractQueuedSynchronizer {
   
private static final long serialVersionUID = 4982264981922014374L;

   
Sync(int count) {//设置状态,被等待的线程数
        setState(count);
   
}

   
int getCount() {
       
return getState();
   
}

   
protected int tryAcquireShared(int acquires) {
       
return (getState() == 0) ? 1 : -1;//如果线程数为0,则返回1
    }

   
protected boolean tryReleaseShared(int releases) {
       
// Decrement count; signal when transitionto zero
       
for (;;) {
           
int c = getState();
            if
(c == 0)
               
return false;
            int
nextc = c-1;
            if
(compareAndSetState(c, nextc))//CAS操作
                return nextc == 0;
       
}
    }
}

构造函数

私有成员privatefinal Sync sync,也就是上面的同步器。构造函数传入一个count,表示需要等待count个线程完成后才能继续执行。

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);//静态内部类中构造函数
}

方法

void await() //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行,除非被中断。这里sync.acquireSharedInterruptibly(1);里面调用了tryAcquireShared(1),从上面的源码来看return (getState() == 0) ? 1 : -1;也就是线程数(状态)为0时才返回1。如果还有线程未结束,则调用AQS中的doAcquireSharedInterruptibly(intarg)方法,进入自旋,也就是等待状态变为0才能继续运行。

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

boolean await(longtimeout, TimeUnit unit) 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行。

public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

void countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

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

l  long getCount()返回当前计数。这个没啥好说的。

public long getCount() {return sync.getCount();}

l  String toString()返回标识此锁存器及其状态的字符串。重写toString方法,也没啥好说的。

public String toString() {return super.toString() + "[Count = " + sync.getCount() + "]";}

CyclicBarrier

java.util.concurrent包下一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点(common barrierpoint)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的barrier。

例子

public class test_CyclicBarrier {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep((long) (Math.random()*5000));      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();//所有线程都达到Barrier再继续执行后续代码
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕"+Thread.currentThread().getName()+"继续处理其他任务...");
        }
    }
}

输出:从结果可以看出写完的顺序有快有慢,但当所有都写完后,所有线程才开始“同时”处理后续任务。

线程Thread-0正在写入数据...

线程Thread-1正在写入数据...

线程Thread-2正在写入数据...

线程Thread-3正在写入数据...

线程Thread-1写入数据完毕,等待其他线程写入完毕

线程Thread-0写入数据完毕,等待其他线程写入完毕

线程Thread-2写入数据完毕,等待其他线程写入完毕

线程Thread-3写入数据完毕,等待其他线程写入完毕

所有线程写入完毕Thread-3继续处理其他任务...

所有线程写入完毕Thread-1继续处理其他任务...

所有线程写入完毕Thread-2继续处理其他任务...

所有线程写入完毕Thread-0继续处理其他任务...

源码

几个私有成员

/**CyclicBarrier可以多次使用,每次均表示为一代生成的实例。每当Barrier触发时会重置(=false)或者产生新一代(new Generation()*/
private static class Generation {boolean broken = false;}//只有一个boolean变量

/** 防护屏障入口锁 */
private final ReentrantLock lock = new ReentrantLock();
/** 等待,直到trip的条件 */
private final Condition trip = lock.newCondition();
/** 加入线程的数量 */
private final int parties;
/* 当达到trip条件时候运行的Runnable线程(如果设置有的话) */
private final Runnable barrierCommand;
/** 当前的一代 */
private Generation generation = new Generation();

/**仍在运行未达到trip条件的线程数(从parties0*/
private int count;

构造函数,有两个

CyclicBarrier(intparties)

创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作,传入的Runnable是null。

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

CyclicBarrier(intparties, Runnable barrierAction)

创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。这个说法其实有歧义,看private int dowait这个方法的源码可以知道,是最后一个完成到达barrier的线程执行,但是执行的内容是传入的Runnable对象的run()方法,注意这里不是新启动了一个线程,而是直接调用的run()方法

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

几个私有方法

/**进入下一代*/
private void nextGeneration() {
    // 唤醒上一代的每个等待trip的线程
    trip.signalAll();
    // 设置下一代,初始化count数量,new Generation
    count = parties;
    generation = new Generation();
}
/**不生成下一代,将broken设置为true,唤醒所有等待trip的线程*/
private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}
/**主要的逻辑流程代码都在这*/
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();//上锁
    try {
        final Generation g = generation;

        if (g.broken)//如果调用了breakBarrier的话,这个会抛异常
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {//线程中断会调用breakBarrier方法
            breakBarrier();
            throw new InterruptedException();
        }

        int index = --count;
        if (index == 0) {  //所有线程都执行到Barrier,只有最后一个线程会进入这段代码
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();//调用的是run()方法,没启动新线程,是最后一个线程
                ranAction = true;
                nextGeneration();//重置CyclicBarrier,可以再次使用
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                if (!timed)
                    trip.await();//到达Barrier的线程等待其他线程到达
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

方法

int await() 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。调用的是上面的dowait()方法。

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

int await(long timeout,TimeUnit unit) 在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。和上面类似,传入了时间单元。

public int await(long timeout, TimeUnit unit)
    throws InterruptedException,
           BrokenBarrierException,
           TimeoutException {
    return dowait(true, unit.toNanos(timeout));
}

int getNumberWaiting() 返回当前在屏障处等待的参与者数目。

public int getNumberWaiting() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return parties - count;
    } finally {
        lock.unlock();
    }
}

int getParties() 返回要求启动此 barrier 的参与者数目。这个没啥好说的。

public int getParties() {return parties;}

boolean isBroken() 查询此屏障是否处于损坏状态。

public boolean isBroken() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return generation.broken;
    } finally {
        lock.unlock();
    }
}

void reset() 将屏障重置为其初始状态。

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}

Semaphore

java.util.concurrent包下一个同步辅助类,代表计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,对数据库连接池的访问。

例子

public class test_Semaphore {
    public static void main(String[] args) {
        int N = 4;            //读数据库线程数
        Semaphore semaphore = new Semaphore(2); //数据库连接数
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }

    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("线程"+this.num+"占用一个数据库连接在读取...");
                Thread.sleep((long) (Math.random()*2000));
                semaphore.release();
                System.out.println("线程"+this.num+"释放数据库连接");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出:

线程0占用一个数据库连接在读取...

线程1占用一个数据库连接在读取...

线程1释放数据库连接

线程3占用一个数据库连接在读取...

线程0释放数据库连接

线程2占用一个数据库连接在读取...

线程2释放数据库连接

线程3释放数据库连接

源码

静态内部类

静态内部类的同步器Sync,继承AQS。同时还包括NonfairSync和FairSync两个非公平和公平的同步器,继承Sync。这里就不贴代码了,理解公平锁和非公平锁原理比较简单。

构造函数

有两个,给定的许可数和是否公平同步器,默认非公平。

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

设置公平的同步器的Semaphore。

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

常用方法

该类的方法较多,这里列举几个常用方法。

l  void acquire()从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。

l  void acquire(int permits)从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。

l  void release()释放一个许可,将其返回给信号量。

l  void release(int permits) 释放给定数目的许可,将其返回到信号量。

acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。release()用来释放许可。注意,在释放许可之前,必须先获。这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:

l  boolean tryAcquire()仅在调用时此信号量存在一个可用许可,才从信号量获取许可。

l  boolean tryAcquire(int permits)仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。

l  boolean tryAcquire(int permits, long timeout, TimeUnit unit) 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。

l  boolean tryAcquire(long timeout, TimeUnit unit)如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。

总结

CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;

CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

Semaphor一般用于控制对某组资源的访问权限。

猜你喜欢

转载自blog.csdn.net/qq407388356/article/details/79972231