需要Callable的理由
通常,我们有两种方式创建线程,一种方式是继承Thread类,另外一种方式是实现Runnable接口。然而,Runnable方式缺少的一个特性就是,当线程终止的时候,即run运行完成的时候,我们布恩那个让线程返回一个执行结果。为了之处这个特性,在java中就增加了Callable接口。
Callable vs Runnable
- 为了实现Runnable接口,需要实现不返回任何返回值的run方法,而对于callable,我们需要实现在完成的时候,返回接口的call方法,注意,线程不能用Callable创建,只能用Runnbale的方式创建。
- 另外一个区别就是,call方法可以抛出异常。而run方法则不能。
public Object call() throws Exception;
如下是Callable的代码示例,它将在大约0-4秒之后返回一个随机数:
public class CallableExample implements Callable {
@Override
public Object call() throws Exception {
Random generator = new Random();
Integer randomNumber = generator.nextInt(5);
TimeUnit.SECONDS.sleep(randomNumber);
return randomNumber;
}
}
Future
当call方法执行完成的时候,计算结果必须存储在main线程的已知对象中,以便mian线程可以值得这个call线程的返回结果,但是程序在此后将如何存储和获得这个结果呢?
为此,我们需要使用Future丢箱,可以将Future看作一个持有结果的对象,它可能现在不持有结果,但是将来,一旦Callable执行完成,就会这样做,因此,Futrue基本上是利用主线程跟踪其他线程结果的一种方式,要实现这个接口,需要重写5个方法,以下示例是其具体实现,在此,我们只列出了重要的方法。
需要注意的是,Callable和Future做了两件不同的事情,Callable和Runnable类似,因为它封装了一个任务,该任务在另外一个线程上运行,而Future用于存储从另外一个线程获得的结果,事实上,未来也可以使用Runnable,这一点在Executors参与之后就会变得很清晰。
- public boolean cancel(boolean mayInterrupt): 用于停止任务,如果任务尚未启动,他将停止该任务,如果任务已经启动,则在manInteerrupt为ture的时候中断该任务。
- public Object get() throws InterruptedException, ExecutionException:用于获得任务的结果,如果任务完成,则立即返回结果,否则等待任务完成,然后返回结果。
- public boolean isDone():如果任务完成,则返回true,否则,返回false。
如果需要创建线程,那么细羽一个Runnable,如果需要取得结果,那么需要一个Future。
在java中,具体的类似是FutureTask,它实现了Runnable和Future,方便地结合了这两种功能。
FutureTask可以通过为其提供的构造函数来创建Callable,然后将FutureTask对象提供给Thread的构造函数来创建Thread对象。因此,间接的实现了Callable创建线程。在此要重点强调的是,没有办法直接用Callable创建线程。
如下是Callable和FutureTask的完整示例:
public class CallableExample implements Callable {
@Override
public Object call() throws Exception {
Random generator = new Random();
Integer randomNumber = generator.nextInt(5);
TimeUnit.SECONDS.sleep(randomNumber);
return randomNumber;
}
}
public class CallableFutureTest {
public static void main(String[] args) throws Exception
{
// FutureTask is a concrete class that
// implements both Runnable and Future
FutureTask[] randomNumberTasks = new FutureTask[5];
for (int i = 0; i < 5; i++)
{
Callable callable = new CallableExample();
// Create the FutureTask with Callable
randomNumberTasks[i] = new FutureTask(callable);
// As it implements Runnable, create Thread
// with FutureTask
Thread t = new Thread(randomNumberTasks[i]);
t.start();
}
for (int i = 0; i < 5; i++)
{
// As it implements Future, we can call get()
System.out.println(randomNumberTasks[i].get());
// This method blocks till the result is obtained
// The get method can throw checked exceptions
// like when it is interrupted. This is the reason
// for adding the throws clause to main
}
}
}
执行结果:
2
2
3
4
0
线程启动后与它所有的交互都使用FutureTask对象来实现Future接口,因此,不需要存储线程对象,使用TutureTask对象,可以实现对task的取消,以及检查它释放完成或者尝试获得其执行结果。
如下是通过Runnable实现类型功能的代码:
public class RunnableExample implements Runnable {
private Object result = null;
@Override
public void run() {
Random generator = new Random();
Integer randomNumber = generator.nextInt(5);
// As run cannot throw any Exception
try {
Thread.sleep(randomNumber * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Store the return value in result when done
result = randomNumber;
// Wake up threads blocked on the get() method
synchronized (this) {
notifyAll();
}
}
public synchronized Object get()
throws InterruptedException {
while (result == null) {
wait();
}
return result;
}
}
public class RunnableTest {
public static void main(String[] args) throws Exception {
RunnableExample[] randomNumberTasks = new RunnableExample[5];
for (int i = 0; i < 5; i++) {
randomNumberTasks[i] = new RunnableExample();
Thread t = new Thread(randomNumberTasks[i]);
t.start();
}
for (int i = 0; i < 5; i++) {
System.out.println(randomNumberTasks[i].get());
}
}
}
输出:
4
4
0
2
3
参考
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html
https://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/FutureTask.html