java并发编程之CountDownLatch,CyclicBarrier和Semaphore

老子叫甜甜 提交于 2020-01-24 19:05:20

一、CountDownLatch

CountDownLatch能够让一个线程在等待其他线程全部完成各自任务后再执行。而CountDownLatch是通过计数器来实现的,计数器的初始值即为任务的总数。
举个例子,如,同学聚会结束回家,每个人都要回各自的家,此时计数器的初始值为参加聚会的总人数,而每个人都是一个线程,每个同学到家后,都需要调用countDown方法,对计数器减一,表示完成回家的任务,当所有同学都到家后,主线程才可以执行通知班长全部到家的任务。再比如,所编写的应用程序,希望等待启动框架的线程启动完毕后再执行。

1.1 构造方法

public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

CountDownLatch的构造方法需要一个count参数,代表初始任务的数量,往后每当调用一次(CountDownLatch.countDown()方法,count都减一,当count减到0的时候,调用CountDownLatch.await()方法的线程就可以执行其任务。

除此之外,CountDownLatch还有以下几个方法:

1.await(long timeout,TimeUnit unit)

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

此方法在await()的基础上添加了时间限制,如果调用await的线程在到达该时间后,count仍然没有0,其会继续执行,不再等到count到0。

2. public long getCount()

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

即获取该CountDownLatch当前的count值

二、Demo应用

package com.wml.test1.countdownlatch;

import java.util.concurrent.CountDownLatch;

/**
 * @author Wang
 * @date 2020/1/2317:27
 */
public class CountDownLatchTest {

	//1.
    private static CountDownLatch finishEat=new CountDownLatch(1);
    //2.
    private static CountDownLatch friends=new CountDownLatch(10);
    public static void main(String[] args) throws InterruptedException {

		//3.
        for (int i = 0; i <10; i++) {
            new Thread(()->{
                try {
                	//4.
                    finishEat.await();
                    System.out.println(Thread.currentThread().getName()+"正在回家");
                    //5.
                    friends.countDown();

	    		System.out.println(Thread.currentThread().getName()+"到家啦");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            },"同学"+(i+1)).start();
        }
        System.out.println("聚会结束,同学们准备回家");
        //6.
        finishEat.countDown();
        //7.
        friends.await();
        //8.
        System.out.println("同学全部到家");
    }
}

说明:
1.代码1创建一个结束聚餐的CountDownLatch,finishEat。初始值为1,代表正在聚餐,当主线程调用countDown()时(代码6),count值从1变为0,代表结束聚餐。这时,其他调用finishEat.await();的线程(各个同学回家的线程)由阻塞状态变为执行。
2.代码2创建一个同学回家的CountDownLatch,friends。初始值为10,代表参加聚会的10个同学,即10个同学需要执行回家任务。回家的任务需要在代码4finishEat.await();后执行。
3.代码3创建10个回家的线程,每个线程中都执行一次friends.countDown();,让friends的count减一
4.代码7friends.await();主线程阻塞等待friends的count变为0时开始执行下面的代码。
结果:

聚会结束,同学们准备回家
同学1正在回家
同学1到家啦
同学2正在回家
同学2到家啦
同学4正在回家
同学4到家啦
同学3正在回家
同学3到家啦
同学5正在回家
同学5到家啦
同学6正在回家
同学6到家啦
同学全部到家

三、CyclicBarrier

Cyclic(循环的)Barrier(屏障),该工具做的事情是,当一个线程到达一个屏障(同步点/临界点)时会被阻塞,等待到最后一个线程到达该点后,被拦截阻塞的线程才可以继续执行。
比如:同学聚餐,不会是到一个就吃一个,而是到的人先等待,直到所有人都达到饭桌后,才开始吃饭。这里餐桌就类似barrier,每个同学都是一个线程,每个人到饭桌后都被阻塞,直到最后一个同学到达。
而因为CyclicBarrier是可循环的,当一组线程到达后,其仍然有效,可以继续下一组循环。

3.1构造方法

1.默认构造方法

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

传入的参数表示需要拦截的线程总数,每当一个线程调用CyclicBarrier.await()方法后,会通知CyclicBarrier该线程已到达屏障。
与CountDownLatch一样,CyclicBarrier也为await提供了超时时间设置。

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

线程阻塞直到到达超时时间。
2.高级构造方法

public CyclicBarrier(int parties, Runnable barrierAction)

该方法使得线程到达障碍后,优先执行barrierAction的操作。

3.2 CyclicBarrier的方法

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

该方法等待所有的线程都到达指定的临界点。
但如果当前线程不是最后到达的线程,则出于线程调度目的将其禁用,并使其处于休眠状态,直到发生以下情况之一:

  1. 最后一个线程到达
  2. 其他线程中断当前线程
  3. 其他线程中断等待线程之一
  4. 在屏障等待时其他线程超时
  5. 其他线程在此屏障上调用reset方法

await方法中调用的dowait方法
以下是dowait源码:

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)
                throw new BrokenBarrierException();
			//代码1
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
			//代码2
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 代码3
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    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();
        }
    }

说明:
代码1:如果当前线程被中断,会调用breakBarrier方法并抛出InterruptedException异常。
breakBarrier方法如下:

private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

1.breakBarrier首先将该代的broken设置为true,代表其被打破。
2.generationCyclicBarrier的一个静态内部类Generation的对象,该对象代表屏障的当前代,可以实现循环等待。broken为true,则该循环结束。
3.同时将count重置为parties
count计数器partiesCyclicBarrier的构造参数,代表拦截的线程数
4.调用tripsignalAll()方法,将所有阻塞的线程唤醒
trip是成员变量Condition的对象,可见是使用Condition实现阻塞队列的。
代码2:
计数器count减一并赋值给int变量index,如果此时index值为0,则判断当前barrierCommand是否为空,(barrierCommand也是CyclicBarrier的成员变量,为换代前优先执行的任务,其在CyclicBarrier构造方法中传入,如下:

 public CyclicBarrier(int parties, Runnable barrierAction) {
       ..........
       ...........
        this.barrierCommand = barrierAction;
    }

),如果不为空,则执行该操作的run()方法。
最后置ranActiontrue,个人理解代表继续执行,并执行nextGeneration()进入下一代(下一个循环)。
nextGeneration()代码如下:

 private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

nextGeneration()中唤醒所有阻塞进程,重置计数器,并重新new了一个Generation。
而在finally中,如果barrierCommand执行出错,或其他原因,会执行breakBa在这里插入代码片rrier()方法。
代码3:
无限循环直到发生tripped,断开,中断或超时。
timed代表是否开启超时时间,nanos 为设定的超时时间

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

返回当前有多少个线程阻塞等待在屏障上

3.3 isBroken
boolean isBroken()

返回查询阻塞等待的线程是否被中断打破

四、CyclicBarrier 例子

package com.wml.test1.cyclicbarrier;

import java.util.concurrent.*;

/**
 * @author Wang
 * @date 2020/1/2317:44
 */
public class CyclicBarrierTest {

	//代码1
    private static CyclicBarrier barrier=new CyclicBarrier(6,()->{
        System.out.println("同学们都到齐了,咱们开饭");
    });

    public static void main(String[] args) {

        ExecutorService pool = new ThreadPoolExecutor(6,100,0L,TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());

        for (int i = 1; i <= 6; i++) {
            pool.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "到了");
                    //代码2
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + "开吃");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }


    }
}

说明:
该例子为同学聚餐,每到一个同学便阻塞自己,当所有同学都到齐后,输出“开饭”,然后唤醒所有同学,执行“吃饭”

代码1:构造一个CyclicBarrier 屏障,拦截线程数为6,在进入下一代前执行System.out.println("同学们都到齐了,咱们开饭");这一barrierCommand,即当前代结束(所有线程都到达屏障时或其他原因)后优先执行该操作。
代码2:
每个线程到达屏障后执行await方法,直到所有线程都到达,await中会执行唤醒所有阻塞线程的方法,最后才会执行后面的代码。
结果:

pool-1-thread-1到了
pool-1-thread-2到了
pool-1-thread-3到了
pool-1-thread-4到了
pool-1-thread-5到了
pool-1-thread-6到了
同学们都到齐了,咱们开饭
pool-1-thread-6开吃
pool-1-thread-2开吃
pool-1-thread-3开吃
pool-1-thread-5开吃
pool-1-thread-4开吃
pool-1-thread-1开吃

五、CountDownLatch和CyclicBarrier的异同

1.CountDownLatch技术器只能使用一次,CyclicBarrier的可以循环使用。
2.CountDownLatch是一个线程等待所有线程完成任务后才会执行自己的任务,是阻塞工作线程。CyclicBarrier是所有线程到达某个屏障(临界点)后互相等待,直到所有线程都到达后再共同进行下面的任务。
3.CountDownLatch调用countDown方法可以继续执行后面的,只是调用await方法的线程会阻塞等待直到所有线程都调用countDown;CyclicBarrier是所有的线程调用await进行自阻塞,等到所有线程都调用一次await后一起继续执行后面操作。
4.CyclicBarrier可以在所有线程都到达屏障后执行barrierAction操作,完成复杂的逻辑场景。

六、Semaphore

Semaphore即信号量,是用来控制同时访问特定资源的线程数量,或同时执行某个指定操作的数量。比如可以用来实现数据库的连接池等。
如数据库的连接数为20个,这时只能有20个线程同时获取数据量连接,再多的线程只能阻塞等待,当一个线程归还连接后,阻塞的线程才能继续获取。

Semaphore的构造方法:

 public Semaphore(int permits)

传入一个整型permits,代表可用的许可证数量。操作时必须先获取许可证,才能继续操作,当操作完成后需释放许可证,其余没有获得许可证的便阻塞等待,直到有线程释放了许可证。
线程使用Semaphore的acquire()方法获取许可证,使用release()方法释放许可证,使用tryAcquire()方法尝试获取许可证.

其他方法:

intavailablePermits() //返回此信号量中当前可用的许可证数。
intgetQueueLength() //返回正在等待获取许可证的线程数。
booleanhasQueuedThreads()//是否有线程正在等待获取许可证。
void reducePermits( int reduction) //减少 reduction 个许可证
Collection getQueuedThreads() //返回所有等待获取许可证的线程集合
semaphore示例:
package com.wml.test1.semphore;

import java.sql.Connection;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.*;

/**
 * @author Wang
 * @date 2020/1/2416:31
 */
public class SemphoreTest {
    private final static int POOL_SIZE=10;
    private final Semaphore connections;
    //连接池
    private static LinkedList<Integer>pool;
    public SemphoreTest() {
        pool=new LinkedList<>();
        //初始化连接池
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(i);
        }
        this.connections = new Semaphore(POOL_SIZE);
    }
	//释放连接
    public void release(Integer conn){

        if(conn!=null) {
            System.out.println("当前有"+connections.getQueueLength()+"个线程等待数据库连接!!"
                    +"可用连接数:"+connections.availablePermits());
            synchronized (pool) {
                pool.addLast(conn);
            }
            connections.release();
        }
    }
	//获取连接
    public Integer acquire() throws InterruptedException {
        connections.acquire();
        Integer conn;
        synchronized (pool){
            conn=pool.removeFirst();
        }
        return conn;
    }

    public static void main(String[] args) {
        SemphoreTest dbPool=new SemphoreTest();
        ExecutorService pool = new ThreadPoolExecutor(50,100,0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
		//50个线程获取连接
        for (int i = 0; i < 50; i++) {
            pool.execute(()->{
                try {
                    Integer conn=dbPool.acquire();
                    System.out.println(Thread.currentThread().getName()
                            +"--------获取数据库连接");
                    Thread.sleep(1000);
                    System.out.println("归还连接");
                    dbPool.release(conn);
                } catch (InterruptedException e) {
                }
            });
        }

    }
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!