介绍
Java中一共有4种创建线程的方式,继承Thread、实现Runnable接口、实现Callable接口,通过线程池创建。在以上4种线程的创建方式中,如果你了解果前两种线程的创建方法,那我们是可以模拟实现Callable接口提供的功能的。它旨在通过异步操作之后,主线程能接收异步操作返回的值。笔者在之前的文章中介绍过什么是闭锁,以及如何模拟实现CountDownLatch闭锁,若不知道该内容的同学可以参考笔者之前写的一篇博文,附上地址:https://blog.csdn.net/qq_25956141/article/details/89931707。
原理
一种常见的场景是异步调用多个接口,同时需要等待各个接口都返回成功,主线程才执行成功的代码,否则执行失败的代码。笔者还是以Boss和Worker的例子来简要说明其原理:
Boss下有Worker1,Worker2,Worker3,Boss有一项任务交个他们3人去完成,只有当整个任务都成功,Boss才给他们3发放工资,否则不发放工资。Worker1,Worker2,Worker3决定将任务分成3个部分,并同时开工。Worker1率先完成了工作,给boss说,我已经完成了工作,这时Boss知道还有2个Worker未完成工作,继续等待,当Worker2给Boss说,我已经完成工作了,这是Boss知道还有1个Worker未完成工作,继续等待,直到Worker3给Boss说,我完成了工作内容,但把工作结果失败了。3个Worker都完成了工作,有1个Worker工作失败,导致整个任务失败,Boss决定不给他们发放工资。
如何模拟上述过程?在此之前需要读者先了解闭锁,本文不再做赘述,不了解的读者可以先看笔者之前的博文java闭锁。
代码实现
模拟实现
在原理部分,笔者介绍了其原理,接下来将模拟其实现。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
public class MyCallableTest {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
//表示将任务分成3部分,让3个Worker各执一份
AtomicBoolean task1 = new AtomicBoolean(false);
AtomicBoolean task2 = new AtomicBoolean(false);
AtomicBoolean task3 = new AtomicBoolean(false);
Worker1 worker1 = new Worker1(task1, latch);
Worker2 worker2 = new Worker2(task2, latch);
Worker3 worker3 = new Worker3(task3, latch);
new Thread(worker1).start();
new Thread(worker2).start();
new Thread(worker3).start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(task1.get() && task2.get() && task3.get()){ //都成功才发放工资
System.out.println("发放工资");
}else{
System.out.println("不发放工资");
}
}
private static class Worker1 implements Runnable{
private AtomicBoolean flag = null;
private CountDownLatch latch = null;
public Worker1(AtomicBoolean flag,CountDownLatch latch){
this.flag = flag;
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep(1000); //模拟工作时间
flag.getAndSet(true); //第一个工人工作成功
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
latch.countDown();
}
}
}
private static class Worker2 implements Runnable{
private AtomicBoolean flag = null;
private CountDownLatch latch = null;
public Worker2(AtomicBoolean flag,CountDownLatch latch){
this.flag = flag;
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep(2000); //模拟工作时间
flag.getAndSet(false); //第2个工人工作失败
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
latch.countDown();
}
}
}
private static class Worker3 implements Runnable{
private AtomicBoolean flag = null;
private CountDownLatch latch = null;
public Worker3(AtomicBoolean flag,CountDownLatch latch){
this.flag = flag;
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep(3000); //模拟工作时间
flag.getAndSet(true); //第3个工人工作成功
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
latch.countDown();
}
}
}
}
输出结果:不发放工资
以上主要通过一个原子类AtomicBoolean原子类模拟其实现,当3个task的值都为真时,整个任务成功,否则失败。
使用Callable接口实现
在JUC中提供了Callable接口简化了上述操作,写法与上述相似。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest { //Boss
public static void main(String[] args) throws InterruptedException, ExecutionException {
Worker1 worker1 = new Worker1(); //3个工人
Worker2 worker2 = new Worker2();
Worker3 worker3 = new Worker3();
//将任务拆分成3个部分
FutureTask<Boolean> task1 = new FutureTask<Boolean>(worker1);
FutureTask<Boolean> task2 = new FutureTask<Boolean>(worker2);
FutureTask<Boolean> task3 = new FutureTask<Boolean>(worker3);
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
if(task1.get()&& task2.get() &&task3.get()){ //get是一个阻塞方法,未完成,则不会继续执行
System.out.println("发放工资");
}else{ //有一个或者几个不成功,不发放工资
System.out.println("不发放工资");
}
}
private static class Worker1 implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
Thread.sleep(1000); //模拟工作时间
return true; //第1个工人工作成功
}
}
private static class Worker2 implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
Thread.sleep(2000); //模拟工作时间
return false; //第2个工人工作失败了
}
}
private static class Worker3 implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
Thread.sleep(3000); //模拟工作时间
return true; //第3个工人工作成功
}
}
}
输出结果:不发放工资
JUC提供的Callable接口大大简化了其操作,其需要一个FutureTask维护其操作。
总结
本文介绍了闭锁的另一种实现方法,即Callable接口的模拟与应用,同时用一个Boss和Worker的例子来说明其原理,最后给出了代码实现和输出结果。