futuretask
为什么使用futuretask?
- 简化代码:
FutureTask
是RunnableFuture
的实现,它自身就是一个Runnable
,可以直接提交给Thread
对象执行,同时又包含了Future
的所有特性。这种封装简化了代码,使您不需要手动管理线程的创建和结果的获取。 - 统一的错误处理: 使用
FutureTask
时,任何由Callable
抛出的异常都会被封装在ExecutionException
中,可以在调用FutureTask.get()
时统一处理。如果直接使用Thread
和Callable
,您需要自己管理线程执行和结果的收集,并且处理线程中的异常。 - 更好的取消和控制机制:
FutureTask
提供了方便的取消机制,通过调用cancel()
方法可以取消任务的执行。而使用Thread
和Callable
,您需要自行实现任务取消的逻辑。 - 结果存储:
FutureTask
在内部处理了结果的存储和检索。使用Thread
和Callable
,您需要自己设法存储和访问Callable
返回的结果。 - 阻塞和等待机制:
FutureTask.get()
方法提供了等待任务完成的能力,而且可以选择设置超时。这在使用Thread
和Callable
组合时不那么直接。 - 与并发工具的兼容性:
FutureTask
可以很容易地与 Executor 框架(如ExecutorService
)一起使用,提供更灵活的线程池管理。虽然您也可以将Callable
提交给ExecutorService
,但FutureTask
提供了更多控制和查询执行状态的能力。
总之,虽然在某些情况下使用 Thread
和 Callable
可以达到相似的效果,但 FutureTask
提供了一个更简洁、更强大的解决方案,尤其是在需要任务的取消、结果的存储和检索、异常处理以及与线程池协作等方面
Java中的FutureTask
是一个非常有用的并发工具,它实现了Future
接口和Runnable
接口。以下是FutureTask
的一些优缺点:
优点
- 异步计算:
FutureTask
可以异步执行一个计算任务,并且可以在未来的某个时刻获取其结果。这对于改善程序的响应性和性能非常有帮助。 - 结果重用:一旦
FutureTask
完成,就可以多次且方便地获取其结果,而无需重新计算。 - 异常处理:如果在任务执行过程中发生异常,该异常会被捕获并在调用
get()
时重新抛出。 - 取消操作:可以通过
cancel()
方法取消任务的执行。如果任务已经开始,根据cancel
方法的参数,可以选择是否中断正在执行的任务。 - 超时机制:通过
get(long timeout, TimeUnit unit)
方法,可以设置获取结果的超时时间。
缺点
- 复杂性:使用
FutureTask
比简单的线程执行复杂,需要正确管理任务的提交、执行、取消和结果处理。 - 资源消耗:与直接执行任务相比,
FutureTask
需要更多的系统资源,因为它涉及到线程管理和上下文切换。 - 阻塞调用:
get()
方法是阻塞的,会导致调用线程等待直到任务完成。这可能导致性能问题,尤其是在任务执行时间很长的情况下。 - 限制性:
FutureTask
提供的控制机制相对基础,对于更复杂的并发场景,可能需要更高级的工具,如CompletableFuture
。 - 取消的限制:一旦任务开始执行,取消任务可能并不总是可行的,尤其是当任务不响应中断时。
基本使用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class class1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> f = new FutureTask<>(new MyThread2());
Thread t1 = new Thread(f);
t1.setDaemon(true);
t1.start();
System.out.println(f.get()); // 调用get方法后会一直阻塞
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "主线程");
}
}
class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<String>{
@Override
public String call() throws Exception {
return "dd";
}
}
get方法只等待3秒
import java.util.concurrent.*;
public class class1 {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> f = new FutureTask<>(new MyThread2());
Thread t1 = new Thread(f);
t1.setDaemon(true);
t1.start();
System.out.println(f.get(3,TimeUnit.SECONDS));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "主线程");
}
}
class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<String>{
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(5000);
return "dd";
}
}
会抛出一下异常
isDone轮询方式
import java.util.concurrent.*;
public class class1 {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> f = new FutureTask<>(new MyThread2());
Thread t1 = new Thread(f);
t1.setDaemon(true);
t1.start();
// -------------------- 轮询方式进行
while(true){
if(f.isDone()){
System.out.println(f.get());
break;
}else{
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("等一会");
}
}
// ------------------------------
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "主线程");
}
}
class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<String>{
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return "dd";
}
}
优点
- 非阻塞:轮询提供了一种非阻塞方式来检查任务的完成状态,这意味着你可以在等待任务完成的同时执行其他操作。
- 灵活性:你可以控制轮询的频率和逻辑,根据具体需求调整轮询机制。
缺点
- 资源消耗:频繁的轮询可能会消耗系统资源,特别是当轮询间隔很短时。
- 响应时间:轮询机制可能会引入延迟,因为任务可能在轮询检查之间已经完成。
- 复杂性增加:相比直接调用
get()
阻塞等待结果,轮询增加了代码的复杂性。