摘要:声明了几种方法,其中有一个就是传入声明了对具体的或者任务执行进行取消查询结果获取等方法。事实上,是接口的一个唯一实现类。使用示例第一种方式是使用继承了的线程池中的方法,将直接提交创建。
创建线程的两种方式
直接继承 Thread
实现 Runnable 接口
这两种方式都有一个缺点:在执行完成任务之后,无法直接获取到最后的执行结果。如果需要获取执行结果,就必须通过共享变量或线程通信的方式来达到想要的效果,较为麻烦。
所以从 Java 1.5 起,就提供了两种方式:Callable 和 Future,通过它们可以在任务执行结束后得到任务执行结果。
Runnable 与 Callable首先是 java.lang.Rannable,它是一个接口,里面只声明了一个 run() 方法:
</>复制代码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
由于 run() 方法的返回值类型为 void,所以在线程执行完后无任何返回结果。
然后是 java.util.concurrent.Callable,它也是一个接口,也只声明了一个方法,其名为 call():
</>复制代码
@FunctionalInterface
public interface Callable {
V call() throws Exception;
}
可以看到,这是一个泛型接口,返回的类型就是传进来的 V 类型。
那么,如何使用 Callable 呢?一般是结合 ExecutorService 来使用。ExecutorService 声明了几种 submit 方法,其中有一个就是传入 Callable:
</>复制代码
Future submit(Callable task);
Future
Future 声明了对具体的 Runnable 或者 Callable 任务执行进行取消、查询、结果获取等方法。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。
</>复制代码
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 提供了三种功能:
取消任务:参数表示是否允许中途取消(中断)
判断状态:是否已取消、是否已完成
获取结果:两种方式,指不指定时间
因为 Future 只是一个接口,所以无法直接用来创建对象,因此有了下面的 FutureTask。
FutureTask首先看下 FutureTask 的继承关系:
可以看出 RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 实现了 RunnableFuture 接口。所以它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。
然后可以看到 FutureTask 内部有这几种状态:
</>复制代码
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
再根据注释,可以得知当创建一个 FutureTask 对象时,初始状态是 NEW,在运行过程中,运行状态仅在方法set,setException 和 cancel 中转换为终端状态。有四种状态转换过程:
NEW -> COMPLETING -> NORMAL:正常执行并返回结果(run 执行成功再设置状态为 COMPLETING)
NEW -> COMPLETING -> EXCEPTIONAL:执行过程中出现异常(setException 先设置状态为 COMPLETING)
NEW -> CANCELLED:执行前被取消
NEW -> INTERRUPTING -> INTERRUPTED:执行时被中断(cancel 参数为 true 才可能出现这个状态)
最后来看下 FutureTask 的两个构造器:
</>复制代码
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
可知传入的任务最后都是付给内部的 private Callable
事实上,FutureTask 是 Future 接口的一个唯一实现类。
使用示例 Future</>复制代码
更多关于 FutureTask 的源码分析,可以看 FutureTask源码解析 这篇文章。
第一种方式是使用继承了 ExecutorService 的线程池 ThreadPoolExecutor 中的 submit 方法,将 Callable 直接提交创建 Future。
</>复制代码
import java.util.concurrent.*;
public class FutureExample {
static class MyCallable implements Callable {
@Override
public String call() throws Exception {
System.out.println("do something in callable");
Thread.sleep(5000);
return "Ok";
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(new MyCallable());
System.out.println("do something in main");
Thread.sleep(1000);
String result = future.get();
System.out.println("result: " + result);
}
}
FutureTask
第二种方法是创建一个写好 Callable 的 FutureTask 对象实例,再 submit。因为 FutureTask 内部本身拥有 run 方法,也可以直接创建线程 Thread 运行。
利用 ExecutorService
</>复制代码
import java.util.concurrent.*
public class FutureTaskWithExecutorService {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask futureTask = new FutureTask<>(() -> { // java 8 函数式写法
System.out.println("do something in callable");
Thread.sleep(5000);
return "Ok";
});
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(futureTask);
System.out.println("do something in main");
Thread.sleep(1000);
String result = futureTask.get();
System.out.println("result: " + result);
}
}
利用 Thread
</>复制代码
import java.util.concurrent.*
public class FutureTaskWithThread {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask futureTask = new FutureTask<>(new Callable() {
@Override
public String call() throws Exception {
System.out.println("do something in callable");
Thread.sleep(5000);
return "Ok";
}
});
new Thread(futureTask).start();
System.out.println("do something in main");
Thread.sleep(1000);
String result = futureTask.get();
System.out.println("result: " + result);
}
}
参考资料
Java并发编程:Callable、Future和FutureTask
FutureTask源码解析
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/76857.html
摘要:线程的启动与销毁都与本地线程同步。操作系统会调度所有线程并将它们分配给可用的。框架的成员主要成员线程池接口接口接口以及工具类。创建单个线程的接口与其实现类用于表示异步计算的结果。参考书籍并发编程的艺术方腾飞魏鹏程晓明著 在java中,直接使用线程来异步的执行任务,线程的每次创建与销毁需要一定的计算机资源开销。每个任务创建一个线程的话,当任务数量多的时候,则对应的创建销毁开销会消耗大量...
摘要:目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行时未知的。函数的官方解释是意思是使调用该函数的线程让出执行时间给其他已就绪状态的线程。 线程允许在同一个进程中同时存在多个程序控制流,即通过线程可以实现同时处理多个任务的功能。线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器、栈以及局部变量。 多线程的实...
摘要:一个线程池包含很多准备运行的空闲线程,每当执行完毕后,线程不会死亡而是回到线程池准备为下一个请求提供服务。另一个使用线程池的理由是减少并发线程数。创建大量线程会大大降低性能甚至拖垮虚拟机。 【Future的概念 interface Future ,表示异步计算的结果,Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常...
摘要:一使用线程池的好处线程池提供了一种限制和管理资源包括执行一个任务。每个线程池还维护一些基本统计信息,例如已完成任务的数量。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。使用无界队列作为线程池的工作队列会对线程池带来的影响与相同。 历史优质文章推荐: Java并发编程指南专栏 分布式系统的经典基础理论 可能是最漂亮的Spring事务管理详解 面试中关于Java虚拟机(jvm)的问...
阅读 2304·2019-08-30 15:56
阅读 3131·2019-08-30 13:48
阅读 1152·2019-08-30 10:52
阅读 1528·2019-08-29 17:30
阅读 447·2019-08-29 13:44
阅读 3621·2019-08-29 12:53
阅读 1140·2019-08-29 11:05
阅读 2695·2019-08-26 13:24
极致性价比!云服务器续费无忧!
Tesla A100/A800、Tesla V100S等多种GPU云主机特惠2折起,不限台数,续费同价。
NVIDIA RTX 40系,高性价比推理显卡,满足AI应用场景需要。
乌兰察布+上海青浦,满足东推西训AI场景需要