CountDownlatch
作用:使一个线程等待其他线程执行完成后,才可继续执行,加强版的JOIN。不同于join的是,CountDaownLatch有两个方法,一个是await()用来等待,一个是countDown()用来计数,当countDown计数为0的时候,才会继续执行下面的线程。
举个列子:
在Main方法中,如果我想在main函数运行前,先去初始化两个方法(必须这两个方法执行完成),然后在继续执行main下面的业务。
先看上图:整体是个Main线程。中间横线分割线(相当于围栏),上面是初始化线程,下面是业务线程。每次调用初始化线程计数器-1.只有当计数器=0时候,这个围栏才会大家去执行下面的业务代码。
public class CountDownLacth {
//初始化一个为5的计数器,只有当当前计数器为0,才能让main函数继续往下执行
static CountDownLatch latch = new CountDownLatch(4);
//初始化线程1
public Thread initMethods() {
return new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "正在执行初始化---" );
//执行该初始化线程,计数器-1
SleepTools.sencod(1);
latch.countDown();
//执行该初始化线程,计数器-1
SleepTools.sencod(1);
System.out.println(Thread.currentThread().getName() + "初始化完成---");
});
}
//业务线程
public Thread busisMethods() {
return new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "正在准备执行中......");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行中完毕......" );
});
}
public static void main(String[] args) throws InterruptedException {
System.out.println("main开始执行-------");
CountDownLacth bean = new CountDownLacth();
//这里我们特意把业务线程放在第一个位置去执行,理论上,他应该先打印出来的,但是多次执行的结果,返回他始终在初始化的线程之后才开始执行。
//这就说明了,我们定义的CountDownLatch(4)。只有当计数器为0时候,经过 latch.await();修饰的代码才会被唤醒,继续往下执行
Thread busisMethods = bean.busisMethods();
busisMethods.setName("业务线程");
busisMethods.start();
for (int i = 1; i <5; i++) {
Thread thread = bean.initMethods();
thread.setName("线程【" + i + "】");
thread.start();
}
latch.await();
System.out.println("main执行完毕。。。。");
}
}
main开始执行-------
业务线程正在准备执行中......
线程【1】正在执行初始化---
线程【2】正在执行初始化---
线程【3】正在执行初始化---
线程【4】正在执行初始化---
业务线程执行中完毕......
main执行完毕。。。。
线程【1】初始化完成---
线程【3】初始化完成---
线程【2】初始化完成---
线程【4】初始化完成---
从结果中可以看出来,业务线程经过lacth.await()修饰了,必须等待计数器为0时候,才可以继续往下执行,期间业务线程是出于阻塞状态的。
注意:new CountDownLatch(count)里面的count必须是不小于线程的计数。啥意思呢,就是说,我现在有五个带初始化的线程,每个线程开始初始化计数-1,那我们填写的count就必须>=5。
看下小于5的情况
main开始执行-------
业务线程正在准备执行中......
线程【1】正在执行初始化---
线程【2】正在执行初始化---
线程【3】正在执行初始化---
线程【4】正在执行初始化---
业务线程执行中完毕......
main执行完毕。。。。
线程【6】正在执行初始化---
线程【5】正在执行初始化---
线程【7】正在执行初始化---
观察结果,发现只要计数器为0的时候,业务线程就被唤醒继续向下执行了。
如果大于5为导致业务线程一直处于待唤醒的状态。
main开始执行-------
业务线程正在准备执行中......
线程【1】正在执行初始化---
线程【2】正在执行初始化---
线程【3】正在执行初始化---
线程【4】正在执行初始化---
线程【5】正在执行初始化---
线程【1】初始化完成---
线程【3】初始化完成---
线程【2】初始化完成---
线程【4】初始化完成---
线程【5】初始化完成---
看结果可以发现等待半天也没有看见业务线程被唤醒去执行业务代码。这时候因为计数器在五个线程结束后,他的值是大于0的,让主线程误认为还有其他线程在初始化中,就导致业务线程一直处于阻塞状态。
Semaphore
作用:控制同时方法某个资源的线程数量,一般用于流量控制。
Exchange
作用:两个线程间的数据交换(不常用)
Callable、Future、FutureTask
FutureTask
下面列举常用的用法
- cancel方法,cancel(true)表示中断该线程,false不中断
public class CffThreads {
/*
*检测FutureTask的 cancel方法,cancel(true)表示中断该线程,false不中断
* */
public void futureCancel() throws Exception {
Callable<List<Object>> callable = new Callable<List<Object>>() {
@Override
public List<Object> call() throws Exception {
System.out.println("callable线程开始执行,将先休息三秒,如果未被中断,将执行输出结果.........");
Thread.sleep(3000);
List<Object> list = Arrays.asList("a1", "a2", "a3", "a4");
System.out.println("线程未被中断,结果计算完成,已经返回数据.......");
return list;
}
};
FutureTask<List<Object>> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
//让主线程等待一会。让call线程先执行一会
SleepTools.sencod(1);
Random random = new Random();
if (random.nextBoolean()){
//true表示中断线程
System.out.println("线程已经被中断");
futureTask.cancel(true);
}else {
//fasle表示不中断线程
System.out.println(futureTask.get());
}
}
public static void main(String[] args) throws Exception {
CffThreads cffThreads = new CffThreads();
cffThreads.futureCancel();
}
}
callable线程开始执行,将先休息三秒,如果未被中断,将执行输出结果.........
线程已经被中断
callable线程开始执行,将先休息三秒,如果未被中断,将执行输出结果.........
线程未被中断,结果计算完成,已经返回数据.......
[a1, a2, a3, a4]
两种结果,如果线程正在运行的时候,突然被中断,是不会继续执行下面未执行的代码逻辑的。
2.isDone一般作用于多组线程异步处理,并且需要返回值的时候,可以采用
while(true){
if(A.isDone() && B.isDone()......){
A.get();
B.get();
break;
}
}