java.util.concurrent包下同步工具类

Semaphore

信号量,用来控制对有限资源的访问数量。场景:有限资源的使用限制

构造函数

public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

permits表示初始可用的资源,注意,只是初始值为permits,并不是资源的最大数,通过释放资源的操作可以使可用资源数量超过初始值。

fair等待资源的线程是否采用公平策略获取锁,true即是,先来先得。(公平锁)

acquire()

//申请一个资源
public void acquire() throws InterruptedException 
//申请permits
public void acquire(int permits) throws InterruptedException 

申请资源,当申请的资源>现有可用资源时,申请资源的线程将被阻塞,直到有可用资源或者申请线程被打断,若线程被打断,则抛出InterruptedException异常

acquireUninterruptibly():申请资源,阻塞时不会打断,一直等待申请资源

public void acquireUninterruptibly() 
public void acquireUninterruptibly(int permits) 
申请资源,当申请的资源>现有可用资源时,申请资源的线程将被阻塞,直到有可用资源。并且在此过程中线程不会被打算,将一直等待可用资源。

tryAcquire()

public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException 
public boolean tryAcquire(int permits)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException 
  1. 第一个函数申请一个资源,如果当前有可用资源,立即返回true,否则立即返回false;
  2. 第二个函数申请一个资源,指定一个超时时间,如果当前可以资源数量足够,立即返回true,否则最多等待给定的时间,如果时间到还是未能获取资源,则返回false;如果等待过程中线程被打断,抛出InterruptedException异常;
  3. 和1一样,只是申请permits个资源;
  4. 和2一样,只是申请permits个资源。

release()

public void release() 
public void release(int permits)

第一个函数释放一个资源,第二个函数释放permits个资源。注意,就算现在可用的资源数为0,此时调用release(),将使资源数为1。

availablePermits():获取当前可用资源数

public int availablePermits()

drainPermits():返回当前可用资源数,并清零可用资源数

public int drainPermits()

reducePermits():禁止某些资源不可用

protected void reducePermits(int reduction) 

reduction表示禁止的数量,如果reduction小于零,则抛出IllegalArgumentException异常。注意函数是protected的,同包下才能访问,我感觉平时不常用。

例:

public class TestSemaphore {
    public static void main(String[] args) throws Exception {
        Semaphore wc = new Semaphore(3, true); // 3个坑位
        for (int i = 1; i <= 6; i++) {
            Thread t = new Thread(new Person("第" + i + "个人", wc));
            t.start();
            Thread.sleep(new Random().nextInt(300));
        }
    }

    static class Person implements Runnable {
        private String name;
        private Semaphore wc;

        public Person(String name, Semaphore wc) {
            this.name = name;
            this.wc = wc;
        }

        public void run() {
            System.out.print(name + ":憋死老子了!");
            if (wc.availablePermits() > 0) {
                System.out.println("天助我也,有坑位!");
            } else {
                System.out.println("卧槽,没坑位了,等会儿吧...");
            }
            try {
                wc.acquire(); //申请坑位
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ":终于轮到我了,拉屎就是爽!");
            try {
                Thread.sleep(new Random().nextInt(1000)); // 模拟上厕所时间。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ":拉完了,好臭!");
            wc.release();
        }
    }
}

注意

release函数和acquire并没有要求一定是同一个线程都调用,可以A线程申请资源,B线程释放资源;

调用release函数之前并没有要求一定要先调用acquire函数。

CountDownLatch

countDownLatch是一个同步工具类,join的增强版。允许一个或多个线程,等待其他一组线程完成操作,再继续执行。

//构造函数,初始化计数器值为count,count只能被设置一次
public CountDownLatch(int count);
//调用await()方法的线程会被挂起,直到count值为0才继续执行
public void await() throws InterruptedException;
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(longtimeout, TimeUnit unit)throws InterruptedException;
//将count值减1
public void countDown();

它是通过控制计数器的值来达到等待的目的。当计数器的值>0时,调用countDownLatch.await()会阻塞当前线程,直到其他线程调用countDownLatch.countDown()将计数器的值减到0时,阻塞线程将被唤醒。计数器的值>0时调用await()方法不会阻塞当前线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。

public static void testCountDownLatch(){
        int threadCount = 10;
        final CountDownLatch latch = new CountDownLatch(threadCount);//初始化计数器值 
        for(int i=0; i< threadCount; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程" + Thread.currentThread().getId() + "开始出发");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程" + Thread.currentThread().getId() + "已到达终点");
                    latch.countDown();//将计数器值减1
                }
            }).start();
        } 
        try {
            latch.await();//计数器>0时主线程被阻塞,直到=0被唤醒。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("10个线程已经执行完毕!开始计算排名");
    }

join与countDownLatch区别

join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait,代码片段如下,wait(0)表示永远等待下去。

1while (isAlive()) {
2 wait(0);
3}

直到join线程中止后,线程的this.notifyAll会被调用,调用notifyAll是在JVM里实现的,所以JDK里看不到。

join方法必须要待被等待线程执行结束后,等待线程才可以继续执行。而countDownLatch没有这个线程,只要count减小到0,不管被等待线程是否执行结束,等待线程都可以继续执行(被唤醒,进入可执行状态)。

CyclicBarrier

可循环使用的屏障。它允许一组线程在到达某个栅栏点(await())互相等待,发生阻塞,直到最后一个线程到达栅栏点(到达栅栏点的数量=指定的栅栏数),栅栏才会打开,处于阻塞状态的线程恢复继续执行。它适用于一组线程之间需要互相等待的情况。CyclicBarrier字面理解是循环的栅栏,之所以称之为循环的是因为在等待线程释放后,该栅栏还可以复用。

主要方法:

//构造函数,parties为到达屏障的线程数,当指定的线程值都到达栅栏点时,栅栏打开,线程恢复。如果parties<执行await()的线程数,被阻塞的线程将永远阻塞,因为无法达到指定指定屏障数
public CyclicBarrier(int parties)
//构造函数,同上,barrierAction为到达指定屏障数之后执行的动作。到达指定屏障数后限制性该Runnable,后再继续执行被唤醒的线程
public CyclicBarrier(int parties, Runnable barrierAction) 
//到达屏障,当前线程被阻塞,直到指定数量的线程都到达栅栏点时恢复执行。返回值为在当前线程到达时,还需要wait的线程数
public int await() throws InterruptedException, BrokenBarrierException
//到达屏障,等待指定时间仍不能恢复将抛出异常
public int await(long timeout, TimeUnit unit)
//重置屏障数为初始状态
public void reset()

主要工作原理,通过构造函数创建一个指定屏障数的屏障类,在各线程中调用await(),调用后当前线程将被阻塞,直到,调用的次数到指定屏障数后,所有阻塞的线程将恢复继续执行。

关于两个wait()方法的补充

public int await() throws InterruptedException,BrokenBarrierException
    在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
    如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它,且在发生以下情况之一前,该线程将一直处于休眠状态:
        * 最后一个线程到达;或者
        * 其他某个线程中断当前线程;或者
        * 其他某个线程中断另一个等待线程;或者
        * 其他某个线程在等待 barrier 时超时;或者
        * 其他某个线程在此 barrier 上调用 reset()。 
    如果当前线程:
        * 在进入此方法时已经设置了该线程的中断状态;或者
        * 在等待时被中断 
    则抛出 InterruptedException,并且清除当前线程的已中断状态。
    如果在线程处于等待状态时 barrier 被 reset(),或者在调用 await 时 barrier 被损坏,抑或任意一个线程正处于等待状态,
    则抛出 BrokenBarrierException 异常。
    如果任何线程在等待时被中断,则其他所有等待线程都将抛出 BrokenBarrierException 异常,并将 barrier 置于损坏状态。
    如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作,则在允许其他线程继续运行之前,
    当前线程将运行该操作。如果在执行屏障操作过程中发生异常,则该异常将传播到当前线程中,并将 barrier 置于损坏状态。
    返回:
        到达的当前线程的序列号,其中,getParties() - 1 指示将到达的第一个线程,零指示最后一个到达的线程 
    抛出:
        InterruptedException - 如果当前线程在等待时被中断 
        BrokenBarrierException - 如果另一个 线程在当前线程等待时被中断或超时,或者重置了 barrier,
        或者在调用 await 时 barrier 被损坏,抑或由于异常而导致屏障操作(如果存在)失败。
注意1:返回是"到达的当前线程的序列号",但是本质表示在当前线程到达时,还需要await几个线程。
barrier.await() == barrier.getParties() - 1 表示是第一个到达的线程,barrier.await() == 0表示这是最后一个到达的线程,当然此时它已经跨过了屏障。 至于线程是第几个跨越障碍的线程,这个不清楚,应该和线程的调度有关。
注意2:"如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它。"这里的禁用应该是指不分配时间片段给它吧。
 public int await(long timeout,TimeUnit unit) throws InterruptedException,BrokenBarrierException,TimeoutException
    在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
    如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它,且在发生以下情况之一前,该线程将一直处于休眠状态:
        * 最后一个线程到达;或者
        * 超出指定的超时时间;或者
        * 其他某个线程中断当前线程;或者
        * 其他某个线程中断另一个等待线程;或者
        * 其他某个线程在等待 barrier 时超时;或者
        * 其他某个线程在此 barrier 上调用 reset()。 
    如果当前线程:
        * 在进入此方法时已经设置了该线程的中断状态;或者
        * 在等待时被中断 
    则抛出 InterruptedException,并且清除当前线程的已中断状态。
    如果超出指定的等待时间,则抛出 TimeoutException 异常。如果该时间小于等于零,则此方法根本不会等待。
    如果在线程处于等待状态时 barrier 被 reset(),或者在调用 await 时 barrier 被损坏,
    抑或任意一个线程正处于等待状态,则抛出 BrokenBarrierException 异常。
    如果任何线程在等待时被中断,则其他所有等待线程都将抛出 BrokenBarrierException,并将屏障置于损坏状态。
    如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作,
    则在允许其他线程继续运行之前,当前线程将运行该操作。如果在执行屏障操作过程中发生异常,
    则该异常将传播到当前线程中,并将 barrier 置于损坏状态。
    参数:
        timeout - 等待 barrier 的时间
        unit - 超时参数的时间单位 
    返回:
        到达的当前线程的索引,其中,索引 getParties() - 1 指示第一个将要到达的线程,零指示最后一个到达的线程 
    抛出:
        InterruptedException - 如果当前线程在等待时被中断 
        TimeoutException - 如果超出了指定的超时时间 
        BrokenBarrierException - 如果另一个 线程在当前线程等待时被中断或超时,或者重置了 barrier,
        或者调用 await 时 barrier 被损坏,抑或由于异常而导致屏障操作(如果存在)失败。

public class BarrierDemo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(5);
        final CyclicBarrier barrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            service.execute(new Player("玩家" + i, barrier));
        }
        service.shutdown();
    }
}
public class Player implements Runnable {
    private final String name;
    private final CyclicBarrier barrier;

    public Player(String name, CyclicBarrier barrier) {
        this.name = name;
        this.barrier = barrier;
    }

    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
            System.out.println(name + "已准备,等待其他玩家准备...");
            barrier.await();
            TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
            System.out.println(name + "已加入游戏");
        } catch (InterruptedException e) {
            System.out.println(name + "离开游戏");
        } catch (BrokenBarrierException e) {
            System.out.println(name + "离开游戏");
        }

    }
}

结果

玩家0已准备,等待其他玩家准备...
玩家2已准备,等待其他玩家准备...
玩家1已准备,等待其他玩家准备...
玩家4已准备,等待其他玩家准备...
玩家3已准备,等待其他玩家准备...
玩家4已加入游戏
玩家1已加入游戏
玩家0已加入游戏
玩家3已加入游戏
玩家2已加入游戏

CyclicBarrier与CountDownLatch区别

CountDownLatchCyclicBarrier
减计数方式加计数方式
计算为0时释放所有等待的线程计数达到指定值时释放所有等待线程
无法重置计数达到指定值时,计数置为0重新开始,也可用过调用reset()方法重置
调用countDown()方法计数减一,线程继续执行,调用await()方法只进行阻塞,对计数没任何影响调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
不可重复利用可重复利用
某个或某些线程等待其他线程线程间互相等待
 

Semaphore参考https://blog.csdn.net/hechurui/article/details/49508439

CountDownLatch参考:

http://www.importnew.com/15731.html

https://www.cnblogs.com/bqcoder/p/6089101.html

CyclicBarrier参考

https://blog.csdn.net/hudashi/article/details/7004199

https://blog.csdn.net/yin380697242/article/details/53313622 例子

CyclicBarrierCountDownLatch区别参考:

https://blog.csdn.net/tolcf/article/details/50925145

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值