Java中为什么需要Callable
在java中有两种创建线程的方法:
一种是继承Thread
类,重写run
方法:
-
public
class
TestMain {
-
public
static
void
main(
String[] args) {
-
MyThread t1 =
new
MyThread();
-
t1.
start();
-
}
-
}
-
class
MyThread
extends
Thread {
-
public
void
run(
) {
-
System.
out.
println(
"MyThread running...");
-
}
-
}
-
复制代码
第二种是使用Runnable
创建一个线程:
-
public
class
TestMain {
-
public
static
void
main(
String[] args) {
-
Runnable r1 =
new
Runnable() {
-
@Override
-
public
void
run(
) {
-
System.
out.
println(
"Thread created with runnable running...");
-
}
-
};
-
Thread t1 =
new
Thread(r1);
-
t1.
start();
-
}
-
}
-
复制代码
其实这两种方式,底层都是执行Thread
类的run
方法:
无论使用这里的哪种方式创建线程,都无法在线程结束时return一个返回值。但是在非常多的场景下,我们都需要在线程执行结束时,将执行的结果封装为一个返回值返回给主线程(或者调用者线程)。因此java在1.5版本时,在java.util.concurrent
包引入了Callable
接口,用于线程执行完时return一个返回值。
Callable和Runnable的区别
Runnable和Callable都是接口,分别定义如下:
-
package java.lang;
-
-
/**
-
* The <code>Runnable</code> interface should be implemented by any
-
* class whose instances are intended to be executed by a thread. The
-
* class must define a method of no arguments called <code>run</code>.
-
* <p>
-
* @since JDK1.0
-
*/
-
@FunctionalInterface
-
public
interface
Runnable {
-
public
abstract
void
run
();
-
}
-
复制代码
-
package java.util.concurrent;
-
-
/**
-
* A task that returns a result and may throw an exception.
-
* Implementors define a single method with no arguments called
-
* {@code call}.
-
*
-
* <p>The {@code Callable} interface is similar to {@link
-
* java.lang.Runnable}, in that both are designed for classes whose
-
* instances are potentially executed by another thread. A
-
* {@code Runnable}, however, does not return a result and cannot
-
* throw a checked exception.
-
*
-
* <p>The {@link Executors} class contains utility methods to
-
* convert from other common forms to {@code Callable} classes.
-
*
-
* @see Executor
-
* @since 1.5
-
* @author Doug Lea
-
* @param <V> the result type of method {@code call}
-
*/
-
@FunctionalInterface
-
public
interface
Callable<V> {
-
/**
-
* @return computed result
-
* @throws Exception if unable to compute a result
-
*/
-
V
call
()
throws Exception;
-
}
-
复制代码
可以看出,Callable
和Runnable
主要有两点区别:
- 有返回值;
- 可以抛出异常(这里抛出的异常,会在future.get()时可以通过
ExectionException
捕获);
因此可以看出,Callable
更加实用。这里举个Callable
使用的例子:
-
Callable
callable
=
new
Callable<Integer>() {
-
@Override
-
public Integer
call
()
throws Exception {
-
int
i
=
new
Random().nextInt(
5);
-
try {
-
Thread.sleep(i *
1000);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
return i;
-
}
-
};
-
复制代码
虽然Callable
接口的call
方法可以返回执行结果,但是有两个问题需要解决:
- 线程的创建只能通过
Runnable
,通过Callable
又如何创建线程? - 如何获取执行结果?
答案是Future
和RunnableFuture
。
Future和RunnableFuture
Future
是一个接口,看下定义:
-
package java.util.concurrent;
-
-
/**
-
* A {@code Future} represents the result of an asynchronous
-
* computation. Methods are provided to check if the computation is
-
* complete, to wait for its completion, and to retrieve the result of
-
* the computation. The result can only be retrieved using method
-
* {@code get} when the computation has completed, blocking if
-
* necessary until it is ready. Cancellation is performed by the
-
* {@code cancel} method. Additional methods are provided to
-
* determine if the task completed normally or was cancelled. Once a
-
* computation has completed, the computation cannot be cancelled.
-
* If you would like to use a {@code Future} for the sake
-
* of cancellability but not provide a usable result, you can
-
* declare types of the form {@code Future<?>} and
-
* return {@code null} as a result of the underlying task.
-
*
-
* @see FutureTask
-
* @see Executor
-
* @since 1.5
-
* @author Doug Lea
-
* @param <V> The result type returned by this Future's {@code get} method
-
*/
-
public
interface
Future<V> {
-
-
/**
-
* Attempts to cancel execution of this task. This attempt will
-
* fail if the task has already completed, has already been cancelled,
-
* or could not be cancelled for some other reason. If successful,
-
* and this task has not started when {@code cancel} is called,
-
* this task should never run. If the task has already started,
-
* then the {@code mayInterruptIfRunning} parameter determines
-
* whether the thread executing this task should be interrupted in
-
* an attempt to stop the task.
-
*
-
* <p>After this method returns, subsequent calls to {@link #isDone} will
-
* always return {@code true}. Subsequent calls to {@link #isCancelled}
-
* will always return {@code true} if this method returned {@code true}.
-
*
-
* @param mayInterruptIfRunning {@code true} if the thread executing this
-
* task should be interrupted; otherwise, in-progress tasks are allowed
-
* to complete
-
* @return {@code false} if the task could not be cancelled,
-
* typically because it has already completed normally;
-
* {@code true} otherwise
-
*/
-
boolean
cancel
(boolean mayInterruptIfRunning);
-
-
/**
-
* Returns {@code true} if this task was cancelled before it completed
-
* normally.
-
*
-
* @return {@code true} if this task was cancelled before it completed
-
*/
-
boolean
isCancelled
();
-
-
/**
-
* Returns {@code true} if this task completed.
-
*
-
* Completion may be due to normal termination, an exception, or
-
* cancellation -- in all of these cases, this method will return
-
* {@code true}.
-
*
-
* @return {@code true} if this task completed
-
*/
-
boolean
isDone
();
-
-
/**
-
* Waits if necessary for the computation to complete, and then
-
* retrieves its result.
-
*
-
* @return the computed result
-
* @throws CancellationException if the computation was cancelled
-
* @throws ExecutionException if the computation threw an
-
* exception
-
* @throws InterruptedException if the current thread was interrupted
-
* while waiting
-
*/
-
V
get
()
throws InterruptedException, ExecutionException;
-
-
/**
-
* Waits if necessary for at most the given time for the computation
-
* to complete, and then retrieves its result, if available.
-
*
-
* @param timeout the maximum time to wait
-
* @param unit the time unit of the timeout argument
-
* @return the computed result
-
* @throws CancellationException if the computation was cancelled
-
* @throws ExecutionException if the computation threw an
-
* exception
-
* @throws InterruptedException if the current thread was interrupted
-
* while waiting
-
* @throws TimeoutException if the wait timed out
-
*/
-
V
get
(long timeout, TimeUnit unit)
-
throws InterruptedException, ExecutionException, TimeoutException;
-
}
-
复制代码
可以看出,Future
可以用来表示线程的未来执行结果:一个容器,这个容器内将来存放的是线程的执行结果,线程执行完之前该容器内没有值,但是线程一旦执行成功(Callable
的call
方法返回之后),就会将结果存入该容器。从Future
的接口定义可看出,Future
不仅支持阻塞获取执行结果,还支持取消任务的执行,判断任务是否执行完成等。因此通过Future
,主线程(或者调用者线程)可以跟进子现场的执行情况。
Callable
其实和Runnable
很像,都会执行一个任务,只不过Callable
可以返回执行的结果。一般将执行结果封装到Future
,调用者线程即可以通过Future
获取Callable
的执行结果了。因此,一般Callable
会和Future
搭配使用。
但是问题来了:java创建线程,需要Runnable
,获取执行结果又需要Future
。因此RunnableFuture
来了:
可以看出,通过RunnableFuture
,既可以创建线程,又可以获取线程的执行结果,当然RunnableFuture
也是一个接口,我们一般情况下会使用它的具体实现类FutureTask
。
那可能又有人要问了,Callable
又是如何建立联系的呢?看下FutureTask
的使用方式就明白了:
-
public
class
TestMain {
-
public
static
void
main
(String[] args) {
-
Callable
callable
=
new
Callable<Integer>() {
-
@Override
-
public Integer
call
()
throws Exception {
-
int
i
=
new
Random().nextInt(
5);
-
try {
-
Thread.sleep(i *
1000);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
return i;
-
}
-
};
-
-
/**
-
* callable创建futureTask
-
* FutureTask实现了RunnableFuture接口,因此即是Runnable又是Future
-
* 作为Runnable可以传入Thread创建线程并执行
-
* 作为Future,可以用来获取执行的结果。
-
* 这里创建出来的futureTask对象有人称为"具柄"或者"存根",大家可以理解为用来获取线程执行结果的一个"引用"即可。
-
*/
-
FutureTask<Integer> futureTask =
new
FutureTask<Integer>(callable);
-
-
// 作为Runnable使用
-
Thread
thread
=
new
Thread(futureTask);
-
thread.start();
-
-
try {
-
// 作为Future使用
-
Integer
integer
= futureTask.get();
-
System.out.println(integer);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
catch (ExecutionException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
复制代码
因此FutureTask
是Callable
到Runnable
的桥梁。
不使用Callable和Future,仅使用Runnable实现相同功能
下面我们看下,如果不使用Callable
和Future
,仅使用Runnable
如何实现返回值。
-
public
class
TestMain {
-
public
static
void
main(
String[] args) {
-
MyRunnable myRunnable =
new
MyRunnable();
-
Thread t1 =
new
Thread(myRunnable);
-
t1.
start();
-
Object o = myRunnable.
get();
-
System.
out.
println(o);
-
}
-
}
-
-
class
MyRunnable
implements
Runnable {
-
// 存储执行结果
-
private
Object outCome =
null;
-
-
@Override
-
public
void
run(
) {
-
int i =
new
Random().
nextInt(
5);
-
try {
-
Thread.
sleep(i *
1000);
-
}
catch (
InterruptedException e) {
-
e.
printStackTrace();
-
}
-
// 存储执行结果
-
outCome = i;
-
// 产出结果后唤醒等待的get方法
-
synchronized (
this) {
-
notifyAll();
-
}
-
}
-
-
public synchronized
Object
get(
) {
-
while(outCome ==
null) {
-
try {
-
// 等待产出结果
-
wait();
-
}
catch (
InterruptedException e) {
-
e.
printStackTrace();
-
}
-
}
-
return outCome;
-
}
-
}
-
复制代码
可以看出,通过Runnable
实现更加麻烦,因此这也体现出了Callable
+Future
的优势。
本篇博文主要参考了Callable and Future in Java和Future and FutureTask in java,感兴趣的话可以阅读原文。
转载:https://blog.csdn.net/BASK2311/article/details/127958849