当提起Java实现多线程时候,我们一般来说是继承Thread类或者实现Runnable接口两种。如果不是很严格的说,还有一种就是使用JDK的java.util.concurrent.Callable
首先我们先回顾一下使用Callable实现多线程的步骤:
1:自定义类实现Callble接口,在Callable的call方法中实现多线程要完成的任务。那么问题来了,Callable是什么呢?Callable其实类似于Runnable接口一样,其实例可以被线程执行,具体执行实现是在Runnable中调用了Callable接口的call方法。例如:当我们使用Runnable实现多线程时候,也是自定义类实现Runnable接口之后,创建其实例给Thread构造器,然后启动,代码如下:
public class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("RunnableDemo....");
}
public static void main(String[] args) {
RunnableDemo runnable=new RunnableDemo();
new Thread(runnable).start();
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo implements Callable {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 99; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) {
CallableDemo call=new CallableDemo();
FutureTask ft =new FutureTask<>(call);
new Thread(ft).start();
}
} 这里实现使用了FutureTask,那么FutureTask是做什么的呢?简单说就是FutureTask中持有Callable的实例且FutureTask间接实现了Future和Runnable接口( 准确来说FutureTask实现的是RunnableFuture接口,而RunnableFuture实现的是Future和Runnable接口 ),所以就实现了再Runnable的run方法中调用Callable的call方法。那么Future作用是什么呢?我们首先先看一下Future的JDK源码:
public interface Future {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
Future 其实就是用来表示异步计算的结果。只不过抽象成接口了。
所以证明了当初的结论:Callable其实类似于Runnable接口一样,其实例可以被线程执行。
2:接下来步骤其实就是步骤1中提及到过的FutureTask,创建FutureTask实例,将Callable引用传到其构造器中。由于FutureTask实现了Runnable接口,所以可以被线程执行,进而在线程的run方法中调用call方法完成任务的计算。
如下是JDK的FutureTask的run方法源码:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable c = callable;//获取FutureTask持有的Callable的引用
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();//正式调用Callable的call方法
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
3:最后通过FutureTask实例获取计算结果。
Future的get方法在FutureTask中的实现:(get是一个阻塞方法)
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING) //COMPLETING是FutureTask任务执行的状态
s = awaitDone(false, 0L);//没有计算完,进行等待,具体实现此处不展开
return report(s);//如果计算完毕返回
}
到此为止就完成了使用JDK的Callable实现线程!
接下来我们也模仿JDK的Callable简单实现一下自己的Callable、Future、FutureTask:
/**
* 对应的是JDK中是Callable接口
*
* @author Daxin
*
* @param
*/
public interface Comand {
T call() throws Exception;
}
/**
* 抽象接口,Future表示异步计算的结果。
* @author Daxin
*
* @param
*/
public interface Future {
public T getResult();
public boolean isDone();
}
public class FutureTask implements Runnable, Future {
/**
* 执行的任务
*/
Comand comandImpl = null;
/**
* 计算结果
*/
T result = null;
// 用于获取是否计算完成
private volatile boolean isDone = false;// volatile关键字是为了实时更新isDone数值
public FutureTask(Comand comandImpl) {
super();
this.comandImpl = comandImpl;
}
// 是一个阻塞方法
@Override
public T getResult() {
if (isDone) {
return result;
}
//简单模拟,每每0.2轮询是否计算完毕
while (!isDone) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return result;
}
@Override
public boolean isDone() {
return isDone;
}
@Override
public void run() {
try {
result = comandImpl.call();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
isDone = true;
}
}
/**
* 自己实现类
*
*/
public class ComandImpl implements Comand {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 99; i++) {
Thread.sleep(100);
sum += i;
}
return sum;
}
}
public class Main {
public static void main(String[] args) {
Comand cmd = new ComandImpl();
FutureTask t = new FutureTask(cmd);
new Thread(t).start();
// System.out.println(t.isDone());
// System.out.println(t.getResult());
// System.out.println(t.isDone());
for(;;){
if(t.isDone()){
System.out.println(t.getResult());
break;
}
}
}
}
拓展:
到此如果问Runnable和Callable有什么呢?
1:其中声明的方法不同,一个是run没有返回值,另一个是call有返回值。
2:run方法声明不允许向外抛异常 ,而call方法允许向外抛异常
3:在使用Callable实现线程时候,call方法是在run方法中被调用的