一、Callable
最早创建线程要么是通过实现Runnable接口,或者是继承Thread类来实现(Thread类本身是Runnable的一个实现类),但是都有一个问题:不能携带返回值。
从Java 5开始,提供了一个Callable接口,可以用来提供带返回值的线程,例如:
class CallableDemo implements Callable<String>{
@Override
public String call() throws Exception {
return "Hello World";
}
}
Callable类似于Runnable,只不过对应的方法名是call(),而不是run(),另外不同与run()方法,call()方法需要指定一个返回值,其中的返回类型由声明实现时的Callable中的GenericType指定。要调用一个Callable不能像Runnable一样简单的使用Thread去启动(看看Thread就知道,它根本就不支持使用Callable)。
有几种方法可以启动一个Callable:
- FutureTask
FutureTask可以接收一个Runnable也可以接收一个Callable,从类继承层次:FutureTask -> RunnableTask -> Runnable 可以看出,FutureTask可能入在一个Thread中,所以可以如下启动用一个调用Callable实现的线程:
将Callable放在一个FutureTask中,然后将此FutureTask放在一个Thread中启动;调用FutureTask的get()方法用于得到返回值,此方法会一直阻塞,直到某个异常发生或者线程调用结束,返回值返回。FutureTask<String> futureTask = new FutureTask<String>(new CallableDemo()); new Thread(futureTask).start(); String response = futureTask.get(); System.out.println(response);
- Executors
Executors是一个工厂类,提供了一些通用方法来创建各种线程相关对象,例如将一个Runnable转成Callable;创建一个线程池(ThreadPool/ExecutorService);创建一个线程工厂(ThreadFactory)等。其中ExecutorService类可以用来启动一个Callable线程,例如:
这里用newSingleThreadExecutor()方法是因为只有一个Callable对象要调用。可以换成其他的,例如newFixedThreadPool(int)或newCachedThreadPool()方法,如果还有更多的Callable需要调用。同样的用get()方法得到返回值。CallableDemo callable = new CallableDemo(); ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); Future<String> future = singleThreadExecutor.submit(callable); String response = future.get(); System.out.println(response);
不过如果有大量的线程执行,那么一个一个查就不太方便了,这就可以使用下面介绍的ExecutorCompletionService类了。
二、ExecutorCompletionService
上面说了为了得到线程运行结果,需要查询线程的状态,调用future的get()方法来得到结果,如果线程未结束,则get()方法阻塞。如果有大量的线程在运行,则必须一个一个查询这些线程的状态以得到返回结果,针对此,Java刚好提供了一个ExecutorCompletionService类可以用来做这种查询,ExecutorCompletionService类会把所有运行完的线程放到一个有序队列里去,只要调用它的take()方法,就会从中把结果一个一个取出。
首先修改一下上例中的CallableDemo:
class CallableDemo implements Callable<String>{
private int i;
private CountDownLatch latch;
public CallableDemo(int i, CountDownLatch latch) {
this.i = i;
this.latch = latch;
}
@Override
public String call() throws Exception {
latch.await();
System.out.println(i);
return "Hello World "+i;
}
}
增加了一个变量i,用于区别不同的线程;增加了一个变量latch用于在所以线程准备好之前等待。
然后看看创建端:
CountDownLatch latch = new CountDownLatch(10); //设置门槛为20
//创建一个线程数为5的线程池
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<String>(Executors.newFixedThreadPool(5));
//创建20个callable对象,并用completionService去启动它
for(int i = 0;i<10;i++){
completionService.submit(new CallableDemo(i, latch));
latch.countDown();
}
//从completionService中取结果
for(int i = 0;i<10;i++){
Future<String> take = completionService.take(); //如果没有结果,则等待
System.out.println(take.get());
}
在Callable中打印了一下i,是想用来比较一下是不是先完成先被加到完成队列里。下面是某次的运行结果:
4
0
2
1
3
8
7
6
5
Hello World 0
9
Hello World 4
Hello World 2
Hello World 1
Hello World 3
Hello World 8
Hello World 7
Hello World 6
Hello World 5
Hello World 9
可以看到基本上先执行的先被加到队列里(4和0可能基本是同时结束)。