目录
1、利用CountDownLatch
2、利用Future
最近在面试的时候,经常遇到这个题目,首先从题目上看,就知道考察的是多线程方面知识,我第一次看到这个题目的时候,就想到了使用CountDownLatch这个计数器来实现,因为它的原理就是让一个线程或多个线程去等待另外线程执行完毕后再执行的,本篇文章,我打算用两种方式去实现这个题目,分别是使用CountDownLatch和Future来实现。
1、利用CountDownLatch
如果第一次听说CountDownLatch的话,也没用关系,我会用最通俗易懂的话去介绍。
CountDownLatch其实就是一个计数器,在new这个对象的时候,需要在CountDownLatch的参数中传递一个int类型的数字,并且这个int类型必须大于等于0。
而这个count值传递给Sync后,就会调用一个setState方法,这是AQS里面的内容,过多的我就不去讲述,如果有兴趣的话可以去网上搜一下AQS了解一下。这个state有三种,分别是0、1、大于1,首先0就代表着目前还没有线程去占用这个资源,可以抢占,1代表着目前已经有一个线程抢占着这个资源了,不允许其他线程再进来了,这时其他线程就会被放入到一个双向链表队列进行阻塞等待(可以去了解一下AQS的阻塞队列),大于1呢就是说这个资源被重入的次数。所以,利用该状态码值就可以实现阻塞线程。
接着介绍一下CountDownLatch的常用方法
方法名 | 解释 |
await() | 让线程去阻塞等待 |
countDown() | 使计数器的值-1,直到减为0后,代表所有线程执行完毕 |
getCount() | 获取当前计数器值 |
boolean await(long timeout, TimeUnit unit) | 此方法至多会等待指定的时间,超时后会自动唤醒,若 timeout 小于等于零,则不会等待。 若计数器变为零了,则返回 true;若指定的等待时间过去了,则返回 false |
CountDownLatch(int count) | count为计数器的初始值 |
示例代码
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch c = new CountDownLatch(2);//初始化一个对象,把计数器定为2
new Thread(() -> {
System.out.println("我是线程1");
c.countDown();//计数器减一
}, "t1").start();
new Thread(() -> {
System.out.println("我是线程2");
c.countDown();//计数器减一
}, "t2").start();
c.await();//调用await方法,让线程3等待前两个线程执行完毕
new Thread(() -> {
System.out.println("我是线程3");
}, "t3").start();
}
}
2、利用Future
Future其实就是一个异步任务监视器,可以监视任务的执行,也可以取消任务的执行,同时也可以通过get()方法获取执行后的返回结果。
为了方便演示效果,我使用了有返回结果的Callable来实现。
Future的常用方法
方法 | 解释 |
get() | 此方法就是获取线程池提交任务返回的结果,这个方法会一直阻塞其他线程,任务执行完毕后才会获取值。也可以传递超时时间,如果任务超过的超时时间,就会报错。 |
boolean isDone() | 判断任务是否运行完毕,如果运行完毕就返回true,否则返回false |
boolean isCancelled() | 判断任务是否已被取消,如果是返回true,否则false |
boolean cancel(boolean mayInterruptIfRunning) | 试图取消任务的执行。如果传入的参数是 true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止。 如果传入的是 false 则就代表不中断正在运行的任务,也就是说,本次 cancel 不会有任何效果,同时 cancel 方法会返回 false。 |
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//首先创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//线程池的submit返回值类型就是Future
Future<String> s1 = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(200);//模拟程序运行时间
return "我是线程1";
}
});
Future<String> s2 = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(200);//模拟程序运行时间
return "我是线程2";
}
});
try {
System.out.println(s1.get(1000, TimeUnit.MILLISECONDS));
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
System.out.println(s2.get(1000, TimeUnit.MILLISECONDS));
} catch (TimeoutException e) {
e.printStackTrace();
}
if (s1.isDone() && s2.isDone()) {
Thread.sleep(300);//模拟程序运行时间
System.out.println("我是线程3");
}
executorService.shutdown();
}
}