JUC-Tools
一:减法计数器CountDownLatch
1:概念
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。
CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务
注:CountDownLatch只能够使用一次。线程多次进入等待的场景不适用。
2:CountDownLatch用法
CountDownLatch典型用法:1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
CountDownLatch典型用法:2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发(并发是逻辑上同时执行,并行是物理上同时执行),强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。
3:代码演示
案例一:
外部线程等待(主线程),主线程使用await()方法,等待子线程完成。
package com.test.thread;
import java.util.concurrent.CountDownLatch;
/**
* 减法计数器CountDownLatch
*/
public class CountDownLatchTest {
static CountDownLatch latch = new CountDownLatch(10);//定义10计数器===对应10个线程
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程准备启动");
System.out.println("开始启动依赖服务,子线程");
for (int i=1;i<=10;i++){
new Thread(new Runnable() {
@Override
public void run() {
new ServerStart().startChildren(Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
},"子线程"+i).start();
}
latch.await();
System.out.println("子线程服务加载完毕");
System.out.println("继续主线程启动");
System.out.println("主线程启动完成");
}
}
/**
* 一个线程 必须等待其它线程(一个或多个)结束后才能继续执行
* 可以实现简单线程通信 ;类似于A执行完 唤醒B
*/
class ServerStart {
public void startChildren(String threadName){
System.out.println("子线程"+threadName+"启动完成");
}
}
案例2:
自身线程处于等待,await()方法在自身线程内部
package com.test.thread;
import java.util.concurrent.CountDownLatch;
/**
* 减法计数器
* 第二案例
*/
public class CountDownLatchTest2 {
static CountDownLatch latch = new CountDownLatch(1);//定义一个信号
public static void main(String[] args) throws InterruptedException {
System.out.println("所有选手等待信号枪信号====");
Race race = new Race();
for (int i=1;i<=10;i++){//定义10个选手参赛
new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();//选手处于等待,等待信号枪
} catch (InterruptedException e) {
e.printStackTrace();
}
race.runStart(Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程"+i).start();
}
Thread.sleep(500);
System.out.println("发射信号枪");
latch.countDown();
}
}
/**
* 赛跑
* 多个线程让其处于等待状态,信号枪一响 抢占线程
*/
class Race{
public void runStart(String name){
System.out.println("选手"+name+"开始冲刺");
System.out.println("选手"+name+"到达终点");
}
}
二:加法计数器
1:概念及区别
CyclicBarrier与countDownLatch很相似,都是等待其它线程执行完。但是CyclicBarrier的计数器是可以重置的,从而恢复到初始状态。
CyclicBarrier不需要操作计数器,CyclicBarrier.await()方法自动调整当前位置(由最大向下减)。
CyclicBarrier是线程本身自己处于等待,这样计数器才会变化,CyclicBarrier中await方法本质是调用一次await方法,计数器就会向下减一,直到为0,自动结束计数器中所有线程。
注意:CyclicBarrier初始化数量必须与线程数量一样。
CyclicBarrier.getNumberWaiting(),获取当前线程等待的数量返回的数据并不准确;
2:使用场景
1:countDownLatch使用场景都可满足(主线程等待子线程执行完之后在执行)
2:共同协作(不关注主线程,主要子线程在某个点的相互等待),该情况依赖于reset方法,如下案例:
假设有一家公司要全体员工进行团建活动,活动内容为翻越三个障碍物,每一个人翻越障碍物所用的时间是不一样的。但是公司要求所有人在翻越当前障碍物之后再开始翻越下一个障碍物,也就是所有人翻越第一个障碍物之后,才开始翻越第二个,以此类推比如跨栏比赛,我们修改一下规则,当所有选手都跨过第一个栏杆是,才去跨第二个,以此类推,每一个员工都是一个“其他线程”。当所有人都翻越的所有的障碍物之后,程序才结束。
3:代码
package com.test.thread;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 加法计数器
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
CyclicBarrier barrier = new CyclicBarrier(5);
Game game = new Game();
/*barrier.await();
barrier.getParties()*/
for (int k=1;k<=5;k++){//定义多个人参赛(多个线程)
new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i=1;i<=10;i++){//多个关卡
final String tempI = i+"";
Thread.sleep(200);
game.accross(Thread.currentThread().getName(),tempI+"");
barrier.await();
}
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
},"选手"+k).start();
}
}
}
/**
* 多人一起闯关 每一关必须等待所有人一起闯
*/
class Game{
public void accross(String name,String s){//参赛人姓名(线程名称)
System.out.println(name+"到达第"+s+"关卡");
}
}
注:CountDownLatch与CyclicBarrier区别
-
CountDownLatch 放行由其他线程控制;而CyclicBarrier是由本身来控制的
2:CountDownLatch 的计数器是大于或等于线程数的(一个线程中可以进行多次扣减),而CyclicBarrier是一定等于线程数。
三:Semaphore
1:概念
它是一个计数信号量。概念上它是一个持有很多“许可”的信号量。每一个acquire方法都会被阻塞,直到有可用的许可,而每一个release方法都将产生一个“许可”,进而释放掉阻塞的acquire.
2:使用场景
主要⽤于那些资源有明确访问数量限制的场景,常⽤于限流 。
⽐如:数据库连接池,同时进⾏连接的线程有数量限制,连接不能超过⼀定的数量,当连接达到了限制数量后,后⾯的线程只能排队等前⾯的线程释放了数据库连接才能获得数据库连接。
比如熔断机制。
3:与CountDownLatch区别
加减计数器主要用于线程等待,线程之间简单通信;而Semaphore主要是限制线程同时访问资源的数量,起到许可证的作用。
4:代码
package com.test.thread;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
SourceData data = new SourceData();
for(int i=0;i<10;i++){//10个线程争夺资源
new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
data.test(Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}
},"线程"+i).start();
}
}
}
class SourceData{
void test(String name){
System.out.println(name+"执行资源");
}
}