常用辅助类
CountDownLatch减法计数器
CountDownLatch:减计数器,使用场景:
一间教室有6名学生,放学后一名学生负责锁门。只有等教室里人走完了,才会把门锁上。那怎么有效精准的控制什么时候锁门,使用CountDownLatch减法计数器有效控制。
CountDownLatch主要用法:
CountDownLatch downLatch=new CountDownLatch(6); //设置计数器初始值
countDownLatch.countDown(); 出去一人(产生一个线程)计数器就 -1
countDownLatch.await(); 阻塞等待计数器归零
代码demo
package com.jp.countdownlatchdemo; import java.util.concurrent.CountDownLatch; /** * @className: * @PackageName: com.jp.countdownlatchdemo * @author: youjp * @create: 2020-05-17 14:33 * @description: TODO Juc常用工具类:CountDownLatch 减法计数器的学习 * @Version: 1.0 */ public class CountDownLatchDemo { public static void main(String[] args) { CountDownLatch downLatch=new CountDownLatch(6);//初始值6人,有6人需要出教室 for (int i = 1; i <=6 ; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"线程出教室一人"); downLatch.countDown(); //每出一个人,计数器就减1 },String.valueOf(i)).start(); } try { downLatch.await(); //等待阻塞计数器归零,也就是6人全部都出教室以后 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"线程:学生全部出教室了,可以锁门了"); } }
运行结果:
使用了CountDownLatch减法计数器,确保了教室无人的情况才可以锁门。
CyclicBarrier加法计数器
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
CyclicBarrier提供两个构造方法CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction):
/** * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and which * will execute the given barrier action when the barrier is tripped, * performed by the last thread entering the barrier. * * @param parties the number of threads that must invoke {@link #await} * before the barrier is tripped * @param barrierAction the command to execute when the barrier is * tripped, or {@code null} if there is no action * @throws IllegalArgumentException if {@code parties} is less than 1 */ public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; } /** * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and * does not perform a predefined action when the barrier is tripped. * * @param parties the number of threads that must invoke {@link #await} * before the barrier is tripped * @throws IllegalArgumentException if {@code parties} is less than 1 */ public CyclicBarrier(int parties) { this(parties, null); }
CyclicBarrier构造方法
- CyclicBarrier(int parties)
默认构造方法,参数表示拦截的线程数量。
- CyclicBarrier(int parties, Runnable barrierAction)
由于线程之前的调度是由CPU决定的,所以默认的构造方法无法设置线程执行优先级,CyclicBarrier提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达同步点时,优先执行线程barrierAction,这样可以更加方便的处理一些负责的业务场景。
创建CyclicBarrier后,每个线程调用await方法告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞。接下来我们来看看await方法的具体实现。
- await实现
CyclicBarrier同样提供带超时时间的await和不带超时时间的await:
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)); }
整个await方法的核心是dowait方法的调用,我们来看看dowait的具体实现。
- dowait的前段部分
/** * Main barrier code, covering the various policies. */ 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(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } 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(); } } }
在dowait的前段部分,主要完成了当所有线程都到达同步点(barrier)时,唤醒所有的等待线程,一起往下继续运行,可根据参数barrierAction决定优先执行的线程。
- 在dowait的后段部分
// 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) { 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(); }
在dowait的实现后半部分,主要实现了线程未到达同步点(barrier)时,线程进入Condition自旋等待,直到等待超时或者所有线程都到达barrier时被唤醒。
- 整个dowait
使用ReentrantLock保证每一次操作线程安全;
线程等待/唤醒使用Lock配合Condition来实现;
线程被唤醒的条件:等待超时或者所有线程都到达barrier。
到这里为止,CyclicBarrier的重要实现源码分析就结束了,接下来还是照样给出一个具体的使用案例,方便掌握CyclicBarrier的具体用法。
使用案例
集齐7颗龙珠后,才能进行召唤神龙许愿。
package com.jp.CyclicBarrierDemo; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * @className: * @PackageName: com.jp.CyclicBarrierDemo * @author: youjp * @create: 2020-05-17 15:10 * @description: TDOO 加法计数器的学习 * @Version: 1.0 */ public class CycliBarrierDemo { public static void main(String[] args) { //1.创建CycliBarrier对象 CyclicBarrier cyclicBarrier=new CyclicBarrier(7, new Runnable() { @Override public void run() { System.out.println("集齐7颗龙珠了,神龙召唤成功"); } }); for (int i = 1; i <=7; i++) { final int temp=i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"收集到第"+temp+"颗龙珠"); try { cyclicBarrier.await(); //未满足7颗条件,等待阻塞 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } }
执行结果
CountDownLatch、CyclicBarrier区别
CountdownLatch阻塞主线程,等所有子线程完结了再继续下去。Syslicbarrier阻塞一组线程,直至某个状态之后再全部同时执行,并且所有线程都被释放后,还能通过reset来重用。
CyclicBarrier,让一组线程到达一个同步点后再一起继续运行,在其中任意一个线程未达到同步点,其他到达的线程均会被阻塞。
CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务。
CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了
Semaphore信号量
Semaphore(信号量)用于控制同时访问特定资源的线程数量,以保证合理的使用公共资源。适用场景:限制资源,如抢位置、限流等。
工作原理
以一个停车场是运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。这个停车系统中,每辆车就好比一个线程,看门人就好比一个信号量,看门人限制了可以活动的线程。假如里面依然是三个车位,但是看门人改变了规则,要求每次只能停两辆车,那么一开始进入两辆车,后面得等到有车离开才能有车进入,但是得保证最多停两辆车。对于Semaphore类而言,就如同一个看门人,限制了可活动的线程数。
Semaphore主要方法:
void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void release():释放一个许可,将其返回给信号量。
int availablePermits():返回此信号量中当前可用的许可数。
boolean hasQueuedThreads():查询是否有线程正在等待获取。
示例:
package com.jp.semaphoreDemo; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * @className: * @PackageName: com.jp.semaphoreDemo * @author: youjp * @create: 2020-05-17 16:37 * @description: TODO 信号量demo: 5辆车使用3个停车位,每个停车位停靠10秒钟 * @Version: 1.0 */ public class SemaphoreDemo { public static void main(String[] args) { //资源:3个停车位 Semaphore semaphore=new Semaphore(3); for (int i = 1; i <=5; i++) { new Thread(()->{ try { semaphore.acquire(); //等待获取许可,占用资源 System.out.println(Thread.currentThread().getName()+"号车获取到了停车位"); TimeUnit.SECONDS.sleep(10); //抢到车位后,这这车位上停靠10秒中 } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release();//释放资源 System.out.println(Thread.currentThread().getName()+"号车离开了停车位"); } },String.valueOf(i)).start(); } } }
运行结果:
有兴趣的老爷,可以关注我的公众号【一起收破烂】,回复【006】获取2021最新java面试资料以及简历模型120套哦~