CountDownLatch、CyclicBarrier和 Semaphore 线程控制

CountDownLatch、CyclicBarrier和 Semaphore都属于线程控制器。其有不同的使用场景


CountDownLatch像是一个计数器,适用于一个线程要等待其它线程执行完之后才继续执行的场景:
有以下方法:
CountDownLatch(int count) : 指定线程控制量,即要等待几个线程完毕(准确的说是要执行几次countDown使得count为0)之后等待线程才能继续执行
void countDown():每次执行count减1,可以作为线程的最后一个步表示当前线程执行完毕
void await():等待其它线程执行完毕(count为0)。如果count不为0则一直等待下去直到count为0或线程被中断。
boolean await(long timeout, TimeUnit unit):等待其它线程执行完毕(count为0),参数为超时时间,即超过这个时间等待线程不在等待,任由还没执行完毕的线程继续执行。
其具有返回值。如果是count为0导致的等待结束返回true。超时导致的await结束返回false。
long getCount():当前还在执行的线程数(count)
值得注意的是所谓的线程执行完毕是需要通过countDown知会CountDownLatch。也就是说await是否继续等待的标准是count是否为0而不是其它线程执行状态,如果不在其它线程执行完毕时间执行countDown,那么等待线程就会一直等待下去或超时或等待线程被中断。
通过一个小例子来简单表示CountDownLatch的简单使用:
package sync;

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class CountDownLatchTest {

    public static void main(String[] args) {
        final CountDownLatch cd= new CountDownLatch(2); // 要控制的线程数,即需要countDown的次数
        
        Callable<String> c1 = new Callable<String>() {
            
            @Override
            public String call() throws Exception {
                System.out.println("This Is Callable1");
                Thread.sleep(5000);
                System.out.println("c.countDown");
                cd.countDown(); // 初始count数减1
                return "The ruturn of Callable1";
            }
        };
        FutureTask<String> task1 = new FutureTask<>(c1);
        final Thread t1 = new Thread(task1);
        Callable<String> c2 = new Callable<String>() {
            
            @Override
            public String call() throws Exception {
                System.out.println("This Is Callable2");
                cd.countDown();
                System.out.println("cd count : " + cd.getCount()); // task1在等待,这里已经countDown,因此这里是1
                Thread.sleep(1000);
                System.out.println("中断task1");
                t1.interrupt();   // 另一个线程中断await依然会等待下去
                return "The return of Exception Callable2";
            }
        };
        FutureTask<String> task2 = new FutureTask<>(c2);
        
        try {
            t1.start();
            Thread.sleep(500);  // 睡眠500保证task1进入sleep状态
            new Thread(task2).start(); 
            System.out.println("开始await");
            long ts = System.currentTimeMillis();
            System.out.println(cd.await(3, TimeUnit.SECONDS));  // 只有3个条件能使await停止等待:1.所控制线程执行完毕,即getCount等于0时  
                                                                // 2.超时  3.主线程(await所在的线程)被结束,他所控制的线程如果在执行countDown之前被中断await也不会管,
                                                                //   他依然会等待下去(await只认countDown与超时,你是否被中断他不管),直到超时。
                                                                // count为0导致的等待结束返回true否则返回false
            long te = System.currentTimeMillis();
            System.out.println("等待时间:" + (te - ts));
            System.out.println("END");
        } catch (InterruptedException e) {
            System.out.println("Exception");
        }
    }

}

运行结果:

This Is Callable1
开始await
This Is Callable2
cd count : 1
中断task1
false
等待时间:3003
END

CyclicBarrier:可重复使用的线程控制器、适用于多个线程需到达某个状态之后在同时开始执行的场景,就好似有道屏障,只有在屏障处等待的线程到指定数量这个屏障才会打开:
含有以下方法:
CyclicBarrier(int parties, Runnable barrierAction):构造方法,parties标识要控制的线程数,即当多少个线程在屏障处等待时就放行。barrierAction为需要在放行是同步执行的操作。
CyclicBarrier(int parties):构造方法,parties标识要控制的线程数,即当多少个线程在屏障处等待时就放行。
int getParties():获得要控制的线程数,即当多少个线程在屏障处等待时就放行。
int await():等待屏障处线程达到数量
int await(long timeout, TimeUnit unit):等待屏障处线程达到数量  带超时时间
boolean isBroken():屏障是否已经被破坏  true为已被破坏
void reset():CyclicBarrier可重复使用并不代表屏障每开启一次就要调这个方法一次。每当屏障处到达指定数量的等待线程,屏障会开启并自动关闭等待下一波线程。无需每开启一次就重置一次。这个方法使屏障恢复初始状态,不过如果再重置时有线程在屏障处等待,则抛出BrokenBarrierException.
int getNumberWaiting():获得当前在屏障处等待的线程数
通过一个小例子来简单表示CyclicBarrier的简单使用:
package sync;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CyclicBarrierTest {

    public static void main(String[] args) {
        Runnable allAwaitRun = new Runnable() {
            
            @Override
            public void run() {
                System.out.println("大家都执行完毕了,准备下一步工作喽");
            }
        };
        final CyclicBarrier c = new CyclicBarrier(3, allAwaitRun);  // 参数二为屏障放行之后要启的线程
                                                                    // 如果你实际有3个线程但是屏障设置为2,那么先到的2个会被屏障放行,
                                                                    //最后一个屏障并不知道也不去管,导致最后一个到的一直等待或者超时报TimeoutException
        Runnable r1 = new Runnable() {
            
            @Override
            public void run() {
                System.out.println("线程1开始进行第一步工作...");
                try {
                    Thread.sleep(500); // 等待模式耗时操作
                    System.out.println("线程1第一步工作完毕,进入等待状态...当前等待线程量 - " + c.getNumberWaiting());
                    try {
                        System.out.println("r1.await - " + c.await(2, TimeUnit.SECONDS));  // await返回该线程到达屏障的顺序,最快的为n-1,最慢的为0
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1通过屏障");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        Runnable r2 = new Runnable() {
            
            @Override
            public void run() {
                System.out.println("线程2开始进行第一步工作...");
                try {
                    Thread.sleep(1000);// 等待模式耗时操作
                    System.out.println("c2 - " + c.isBroken());
                    System.out.println("线程2第一步工作完毕,进入等待状态...当前等待线程量 - " + c.getNumberWaiting());
                    try {
                        System.out.println("r2.await - " + c.await(2, TimeUnit.SECONDS));
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程2通过屏障");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        Runnable r3 = new Runnable() {
            
            @Override
            public void run() {
                System.out.println("线程3开始进行第一步工作...");
                try {
                    Thread.sleep(1500);// 等待模式耗时操作
//                    Thread.sleep(5000);// 等待模式耗时操作   5s的话显而易见的会报超时,由先到屏障处即await返回值为n-1的那个线程抛出TimeoutException
                                            //超时异常一经抛出,屏障被破坏,因此会导致其他已在屏障处的等待线程await抛出 BrokenBarrierException.
                                            // (依据对这个异常的处理(catch还是throws(Callable可抛出异常))来判定线程是继续执行还是抛出停止)
                                            //但是对线程3因为此线程延迟5s,所以此时还没到屏障处
                                            // 所以会继续执行,直至到达屏障处发现屏障已被破坏,抛出BrokenBarrierException
                    System.out.println("线程3第一步工作完毕,进入等待状态...当前等待线程量 - " + c.getNumberWaiting());
                    try {
                        System.out.println("r3.await - " + c.await(2, TimeUnit.SECONDS));
                    } catch (BrokenBarrierException e) {
                        System.out.println("e - " + c.isBroken());  //  设置sleep(5000)时屏障被击穿   这里为true
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程3通过屏障");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        new Thread(r1).start();
        new Thread(r2).start();
        new Thread(r3).start();

        System.out.println("c.getParties() - " + c.getParties()); // CyclicBarrier掌控的线程数
        
        c.reset(); // 重置,如果此时有线程在屏障处等待,那么抛出BrokenBarrierException
    }

}

运行结果:

线程1开始进行第一步工作...
线程3开始进行第一步工作...
c.getParties() - 3
线程2开始进行第一步工作...
线程1第一步工作完毕,进入等待状态...当前等待线程量 - 0
c2 - false
线程2第一步工作完毕,进入等待状态...当前等待线程量 - 1
线程3第一步工作完毕,进入等待状态...当前等待线程量 - 2
大家都执行完毕了,准备下一步工作喽
r3.await - 0
线程3通过屏障
r1.await - 2
线程1通过屏障
r2.await - 1
线程2通过屏障

Semaphore:线程多于资源的线程控制器。很像锁同步,和锁同步不同的是锁锁定一个资源,同时只能有一个线程操作这个资源。而Semaphore则是锁定一批资源。同时只允许指定数目的线程执行操作。
也与线程池有一定相似。不同的是线程池等待的线程未运行,而这个线程已经在运行并且在争夺。
适用于资源量小于线程量的情况。比如一个连接只允许同时有10个线程连接,现在有100个线程要执行这个连接操作,那么就可以用Semaphore进行控制。
其内含有以下方法:
Semaphore(int permits):构造方法,入参为许可量,即同时允许几个线程动作。默认为非公平的
Semaphore(int permits, boolean fair):构造方法入参一为许可量,入参二为是否公平竞争  true为公平
void acquire() throws InterruptedException:去获得一个许可,获得不到就一直等。等待过程中被中断报InterruptedException
void acquireUninterruptibly():去获得一个许可,获得不到就一直等,无视中断命令
boolean tryAcquire(): 尝试获得一个许可,如果获得就返回true,没获得就返回false
boolean tryAcquire(long timeout, TimeUnit unit): 在指定时间里尝试获得一个许可,如果获得就返回true,没获得就返回false
void release(): 释放一个许可,无需一定持有许可。即没有持有许可依然可以发出许可释放指令
void acquire(int permits) throws InterruptedException:去获得指定数目的许可,获得不到就一直等。等待过程中被中断报InterruptedException
void acquireUninterruptibly(int permits):去获得指定数目的许可,获得不到就一直等,无视中断命令
boolean tryAcquire(int permits):去获得指定数目的许可,如果获得就返回true,没获得就返回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit):在指定时间里尝试获得指定数目的许可,如果获得就返回true,没获得就返回false
void release(int permits): 释放指定数目的许可,无需一定持有许可。
int availablePermits(): 当前可用许可量
int drainPermits():获得所有的许可并返回获得的许可量
boolean isFair():是否为公平竞争
final boolean hasQueuedThreads():当前是否有线程在竞争
final int getQueueLength():当前竞争队列的长度

通过一个小例子来简单表示Semaphore的简单使用:

package sync;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Semaphore;

public class SemaphoreTest {

    public static void main(String[] args) {
        final Semaphore s = new Semaphore(2);  // 入参为2则为正常的3任务调2个许可流程,为1可验证无需获得许可便可释放许可
        Runnable r1 = new Runnable() {
            
            @Override
            public void run() {
                try {
                    System.out.println("1号请求资源 - " + showNow());
                    s.acquire();
                    System.out.println("1号获得资源 - " + showNow());
                    System.out.println("1号开始工作 - " + showNow());
                    Thread.sleep(5000);
                    System.out.println("1号工作完毕,开始释放资源 - " + showNow());
                    s.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        Runnable r2 = new Runnable() {
            
            @Override
            public void run() {
                try {
//                    Thread.sleep(200);  // 保证执行顺序是1.2.3
                    System.out.println("2号请求资源 - " + showNow());
                    s.acquire();  // 如果把这个地方注释掉,并打开保证释放顺序的代码并设置许可为一个,运行会发现1号先持有资源工作,然后2无需获得许可就可释放许可,使得3在1未执行完毕释放许可时便获得许可开始执行
                    System.out.println("2号获得资源 - " + showNow());
                    System.out.println("2号开始工作 - " + showNow());
                    Thread.sleep(1000);
                    System.out.println("2号工作完毕,开始释放资源 - " + showNow());
                    s.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        Runnable r3 = new Runnable() {
            
            @Override
            public void run() {
                try {
//                    Thread.sleep(500);// 保证执行顺序是1.2.3
                    System.out.println("3号请求资源 - " + showNow());
                    s.acquire();
                    System.out.println("3号获得资源 - " + showNow());
                    System.out.println("3号开始工作 - " + showNow());
                    Thread.sleep(1000);
                    System.out.println("3号工作完毕,开始释放资源 - " + showNow());
                    s.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        new Thread(r1).start();
        new Thread(r2).start();
        new Thread(r3).start();
    }

    private static String showNow(){
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        return sdf.format(new Date());
    }
    
}
运行结果:
2号请求资源 - 17:23:48.107
1号请求资源 - 17:23:48.107
2号获得资源 - 17:23:48.107
3号请求资源 - 17:23:48.107
2号开始工作 - 17:23:48.107
1号获得资源 - 17:23:48.107
1号开始工作 - 17:23:48.107
2号工作完毕,开始释放资源 - 17:23:49.107
3号获得资源 - 17:23:49.108
3号开始工作 - 17:23:49.108
3号工作完毕,开始释放资源 - 17:23:50.108
1号工作完毕,开始释放资源 - 17:23:53.107

参考:Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore

发布了29 篇原创文章 · 获赞 11 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yue_hu/article/details/79164448