上面介绍了多线程启动的两种方式,无论继承Thread还是实现Runnable接口,其内部的run方法都是void,也就是拿不到多线程的执行结果。
考虑下面一个小例子:有一个任务需要计算1-100的和,现代计算机肯定单线程分分钟就计算完成了,加一点限制条件,每执行一次加法操作,cpu都会暂停1秒,现在还觉得单线程计算很快么?现在就需要多线程来计算,比如5个线程同时计算,那么理论上不考虑其他因素,只需要20m即可计算完成。
此时不能继承Thread和实现Runnable接口来实现,因此计算后的返回值获取不到,这时Callable和Future就登场了,可以采用两种方式来实现:
1、使用ExecuteService线程池配置Callable和Future接口来实现,在执行Callable任务后,可以获取一个Future对象,调用其get方法,就可以获取到Callable任务的执行结果;Future是一个接口,提供了三种功能:
- 判断任务是否完成
- 中断任务
- 获取任务结果
这里我们只关心任务结果,即get方法,代码如下(简便起见,停顿时间为100ms,线程数为2):
public class TestCallable {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
ExecutorService pool = Executors.newCachedThreadPool();
CalculateTask task1 = new CalculateTask(1,50);
Future<Integer> future1 = pool.submit(task1);
CalculateTask task2 = new CalculateTask(50,101);
Future<Integer> future2 = pool.submit(task2);
pool.shutdown();
System.out.println("主线程在执行任务");
//这个get方法会阻塞,直到线程结束后返回结果
try {
int r1 = future1.get();
int r2 = future2.get();
int result = r1+r2;
System.out.println("多线程执行结果:" + result);
long end = System.currentTimeMillis();
System.out.println("程序执行时间:" + (end-startTime)/1000);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* 通过实现带泛型的Callable接口就可以将执行后的返回值返回给上层
* 上层通过ExecutorService.submit执行后就能获得Future对象,
* 该对象的get方法就能获取多线程执行后的泛型值
*/
class CalculateTask implements Callable<Integer> {
private int start;
private int end;
public CalculateTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Integer call() throws Exception {
System.out.println("线程" + Thread.currentThread().getName() + " 开启");
int sum = 0;
for(int i=start; i<end;i++) {
sum += i;
//1秒太长了,500吧
Thread.sleep(100);
}
return sum;
}
}
注意,get方法是一个阻塞方法,会等到子线程执行完成,才会继续执行,执行结果:
线程pool-1-thread-1 开启
线程pool-1-thread-2 开启
主线程在执行任务
多线程执行结果:5050
程序执行时间:5
Process finished with exit code 0
第二种,上面的方案是基于线程池进行的提交操作,如果不想用线程池怎么办?可以使用FutureTask类来实现,FutureTask类实现了RunnableFuture接口,RunnableFuture接口继承了Runnable接口和Future接口,FutureTask是Future接口唯一的实现类;
FutureTask实现了Future接口,就可以调用其get方法,获取返回结果;
FutureTask实现了Runnable接口,就可以把它传入Thread类中执行,这样就躲开了线程池;当然Future由于间接实现了Future,用线程池的submit也是可以的。
public class TestFutureTask {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
//第一种方式,通过线程池提交任务,简便起见,直接计算1-100了
ExecutorService pool = Executors.newCachedThreadPool();
CalculateTask task1 = new CalculateTask(1,50);
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(task1);
CalculateTask task2 = new CalculateTask(50,101);
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(task2);
pool.submit(futureTask1);
pool.submit(futureTask2);
pool.shutdown();
/*第二种方式,由于FutureTask实现了RunnableFuture,而FutureRunnable接口
* 而FutureRunnable接口又继承了Future和Runnable接口
* 因此,可以通过Thread的方式启动
*/
FutureTask<Integer> futureTask3 = new FutureTask<Integer>(task1);
Thread thread1 = new Thread(futureTask3);
thread1.start();
FutureTask<Integer> futureTask4 = new FutureTask<Integer>(task2);
Thread thread2 = new Thread(futureTask4);
thread2.start();
System.out.println("主线程执行任务");
try {
System.out.println("第一种方式获取计算结果:" + (futureTask1.get() + futureTask2.get()));
System.out.println("第二种方式获取计算结果" + (futureTask3.get() + futureTask4.get()));
long end = System.currentTimeMillis();
System.out.println("程序执行时间:" + (end-startTime)/1000);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
以上代码提供了两种方式,使用线程池和使用Thread的方式启动,结果是相同的:
线程pool-1-thread-1 开启
线程pool-1-thread-2 开启
线程Thread-0 开启
主线程执行任务
线程Thread-1 开启
第一种方式获取计算结果:5050
第二种方式获取计算结果5050
程序执行时间:5