1.Callable接口介绍
我们在实现多线程的时候一般都是实现Runnable接口,这种方式简单高效,但缺点也同样明显,就是无法在线程结束后返回相应结果,去过需要返回结果,只能手动在多线程中使用公共变量进行传递,容易发生线程安全问题,所以,现在让我们来看一下可以返回信息的Callable接口的多线程具体使用!
Callable接口定义:
public interface Callable<V> {
V call() throws Exception;
}
观察发现:
- 与Runnable接口相同,Callable接口通让只有一个抽象方法,我们只需要实现此方法即可。
- Callable接口是一个泛型接口,泛型的类型即为返回值类型,需要手动指定。
现在我们实战来使用Callable接口来实现一个线程:
public class CallableTest {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Callable<String> callable = ()->{
System.err.println("线程已开始......");
Thread.sleep(5000);
System.err.println("线程即将结束.....");
return "你好";
};
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
long time = (System.currentTimeMillis() - start)/1000;
System.err.println("线程已结束,返回值:" + result + ",用时:" + time);
}
}
不同于Runnable接口直接使用Thread类运行线程,Callable接口需要使用FutureTask类来包装接口,并通过此类来获取结果!
FutureTask类实现了Future接口,我们现在看看此接口的定义:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
- boolean cancel(boolean mayInterruptIfRunning);
此方法用于取消当前线程,并返回是否取消成功 - boolean isCancelled();
此方法用于获取线程是否被取消成功 - boolean isDone();
此方法用于获取线程是否执行完毕 - V get() throws InterruptedException, ExecutionException;
此方法用于在线程执行结束后获取返回结果 - V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
此方法用与获取线程返回结果,如果在指定时间内线程没有结束,则直接返回NULL
FutureTask类同样实现了Runnable接口,所以我们在包装后直接通过Tread类来运行线程,让我们看一下运行结果:
我们可以看到,线程开始运行,但是我们调用get()方法会阻塞主线程的运行,直到线程结束后,返回结果后主线程现场才继续运行。
那么可能就有不少人有这样一个疑问:既然get()方法会阻塞当前线程,那么我们使用Callable接口还有什么意义呢,让我们向下看!
2.Callable接口使用实战
设想一下如果我们需要在项目中农调用一个费时的远程接口,并需要拿到相应的结果!
2.1在所有操作结束后调用get()方法等待结果返回即可:
在我们实际开发中,在Controller中我们可能会调用费时远程接口,并且在调用的同时做一些本地的操作,这个时候就可以在Controller开始时就直接开始线程,并且将所有本地操作都做完之后等待远程接口返回即可!
public class CallableTest
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Callable<String> callable = ()->{
System.err.println("接口开始调用......");
Thread.sleep(5000);//使用延时操作模拟调用远程接口
System.err.println("接口调用结束.....");
return "你好";
};
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
System.err.println("主线程的剩余操作开始.....");
Thread.sleep(2000);
System.err.println("主线程的剩余操作结束.....");
String result = futureTask.get();
long time = (System.currentTimeMillis() - start)/1000;
System.err.println("线程已结束,返回值:" + result + ",用时:" + time);
}
}
因为Callable线程只是在调用get()方法后阻塞线程等待返回,那么如果这个时候主线程仍有一些费时操作(暂时不需要线程结果的),直接执行即可,在所有操作结束后调用get()方法等待返回即可!
2.2配合线程池同时执行多个费时操作
如果在一个Controller中需要调用多个远程接口,就可以使用此方法配合线程池使用,Callable接口线程主要应对的就是这种情况!
public class CallableTest {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Callable<String> c1 = ()->{
Thread.sleep(5000);//使用延时操作模拟调用远程接口
System.err.println("接口1调用结束.....");
return "返回值1";
};
Callable<String> c2 = ()->{
Thread.sleep(1000);//使用延时操作模拟调用远程接口
System.err.println("接口2调用结束.....");
return "返回值2";
};
Callable<String> c3 = ()->{
Thread.sleep(4000);//使用延时操作模拟调用远程接口
System.err.println("接口3调用结束.....");
return "返回值3";
};
Callable<String> c4 = ()->{
Thread.sleep(3000);//使用延时操作模拟调用远程接口
System.err.println("接口4调用结束.....");
return "返回值4";
};
Callable<String> c5 = ()->{
Thread.sleep(2000);//使用延时操作模拟调用远程接口
System.err.println("接口5调用结束.....");
return "返回值5";
};
List<Callable<String>> list = new ArrayList<>();
list.add(c1);
list.add(c2);
list.add(c3);
list.add(c4);
list.add(c5);
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Future<String>> results = executor.invokeAll(list);
executor.shutdownNow();
long time = (System.currentTimeMillis() - start)/1000;
System.err.println("操作结束,用时:" + time);
}
}
我们在一个Controller中分别调用了5个远程接口,那么我们就可以将所有Callable线程对象加入到集合中,使用线程池同时执行!
可以看到,单线程执行需要15秒的方法,使用Callable配合线程池只需要5秒(最长的一个接口用时)
即可完成,并且在执行后,线程池会自动等待所有线程执行完毕后再向下执行,此时我们如果需要拿到结果,循环调用get()方法即可!
for(Future<String> result : results)
{
System.err.println(result.get());
}