Callable与Runnable

先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法:

public interface Runnable {
public abstract void run();
}

由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。 
Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法call():

public  interface  Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}

可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。 
一般情况下Callable是配合ExecutorService来使用的。ExecutorService接口中声明了若干个submit方法的重载版本:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。第一个submit方法里面的参数类型就是Callable。

Future

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

Future类位于java.util.concurrent包下,它是一个接口:

public  interface  Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

在Future接口中声明了5个方法,下面依次解释每个方法的作用:

  • cancel方法用来取消任务:

当你想要取消你已提交给执行者的任务,使用Future接口的cancel()方法。根据cancel()方法参数和任务的状态不同,这个方法的行为将不同:

  • 1、如果这个任务已经完成或之前的已被取消或由于其他原因不能被取消,那么这个方法将会返回false并且这个任务不会被取消。
  • 2、 如果这个任务正在等待执行者获取执行它的线程,那么这个任务将被取消而且不会开始它的执行。如果这个任务已经正在运行,则视方法的参数情况而定。 cancel()方法接收一个Boolean值参数(mayInterruptIfRunning)。如果参数为true并且任务正在运行,那么这个任务将被取消。如果参数为false并且任务正在运行,那么这个任务将不会被取消。

  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

  • isDone方法表示任务是否已经完成,若任务完成,则返回true;

  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;

  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

也就是说Future提供了三种功能:

1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

FutureTask

集Runnable、Callable、Future于一身,它首先实现了Runnable与Future接口,然后在构造函数中还要注入Callable对象(或者变形的Callable对象:Runnable + Result),所以FutureTask类既可以使用new Thread(Runnable r)放到一个新线程中跑,也可以使用ExecutorService.submit(Runnable r)放到线程池中跑,而且两种方式都可以获取返回结果,但实质是一样的,即如果要有返回结果那么构造函数一定要注入一个Callable对象,或者注入一个Runnable对象加一个预先给定的结果 

public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}

FutureTask提供了2个构造器,可以看到其实实现方式是一样的

public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
} public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}

事实上,FutureTask只是Future接口的实现类。

使用示例

1.使用Callable+Future获取执行结果

ExecutorService threadPool =  Executors.newSingleThreadExecutor();
Future<String> future =
threadPool.submit(
new Callable<String>() {
public String call() throws Exception {
Thread.sleep(1000);
return "hello";
};
}
);
System.out.println("....");
try {
System.out.println("get value:" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}

2.使用Callable+FutureTask获取执行结果

Executor框架利用FutureTask来完成异步任务,并可以用来进行任何潜在的耗时的计算。一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; public class TestThreadPool {
public static void main(String[] args) {
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
List<FutureTask<String>> tasks = new ArrayList<FutureTask<String>>();
for (int i = 0; i < 10; i++) {
FutureTask<String> futureTask = new FutureTask<String>(new ThreadPoolTask(i));
threadPool.submit(futureTask);
tasks.add(futureTask);
}
for (FutureTask<String> futureTask : tasks) {
try {
// 阻塞一直等待执行完成拿到结果
System.out.println("future result:" + futureTask.get());
// 阻塞一直等待执行完成拿到结果,如果在超时时间内,没有拿到则抛出异常
// System.out.println("future result:"+futureTask.get(1,TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} // 捕获超时异常
// catch (TimeoutException e) {
// e.printStackTrace();
// }
}
System.out.println("--------------------------");
threadPool.shutdown();
} public static class ThreadPoolTask implements Callable<String> {
private int value;
public ThreadPoolTask(int value) {
this.value = value;
}
public String call() throws Exception {
System.out.println("value-----" + value++);
Thread.sleep(2000);
return String.valueOf(value);
}
}
}

对比使用Callable+Future的实现可知道,使用FutureTask不用去接收submit的返回值,而是自身就继承了Future,相对方便些。

CompletionService

示例

这里先给出个示例

import java.util.Random;
import java.util.concurrent.*; public class CallableAndFuture {
ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool2);
for(int i=1;i<=10;i++){
final int seq = i;
completionService.submit(new Callable<Integer>() {
public Integer call() throws Exception {
Thread.sleep(new Random().nextInt(2000));
return seq;
}
});
}
for(int i=0;i<10;i++){
try {
System.out.println(
completionService.take().get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("======");
threadPool2.shutdown();
} }

使用Callable+Future和FutureTask的实现需要定义一个List来保存Future的值,取值得时候还需要通过for循环遍历,相对来说使用CompletionService更方便。


CompletionService方法

ExecutorCompletionService构造方法:

1、ExecutorCompletionService(Executor executor) 使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将 LinkedBlockingQueue 作为完成队列。

2、ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>>>completionQueue) 使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将所提供的队列作为其完成队列。

返回值 方法
Future<V> take()获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
Future<V> submit(Callable task)提交要执行的值返回任务,并返回表示挂起的任务结果的 Future。
Future<V> submit(Runnable task, V result)提交要执行的 Runnable 任务,并返回一个表示任务完成的 Future,可以提取或轮询此任务。
Future<V> poll()获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回 null。
Future<V> poll(long timeout, TimeUnit unit)获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则将等待指定的时间(如果有必要)。

原理

CompletionService整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take和poll方法,在结果完整可用时获得这个结果,像一个打包的Future。ExecutorCompletionService是实现CompletionService接口的一个类,并将计算任务委托给一个Executor。

ExecutorCompletionService的实现相当直观。它在构造函数中创建一个BlockingQueue,用它去保持完成的结果。计算完成时会调用FutureTask中的done方法。当提交一个任务后,首先把这个任务包装为一个QueueingFuture,它是 FutureTask的一个子类,然后覆写done方法,将结果置入BlockingQueue中,take和poll方法委托给了BlockingQueue,它会在结果不可用时阻塞。

ExecutorCompletionService的一个构造函数,整合了Executor和BlockingQueue的功能:

public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}

任务的提交和执行都是委托给Executor来完成。当提交某个任务时,该任务首先将被包装为一个QueueingFuture,

public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}

QueueingFuture是FutureTask的一个子类,通过改写该子类的done方法,可以实现当任务完成时,将结果放入到BlockingQueue中。

private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}

而通过使用BlockingQueue的take或poll方法,则可以得到结果。在BlockingQueue不存在元素时,这两个操作会阻塞,一旦有结果加入,则立即返回。public Future<V> take() throws InterruptedException {

    return completionQueue.take();
} public Future<V> poll() {
return completionQueue.poll();
}

转载:java并发编程-Callable与Future

JAVA多线程学习十-Callable与Future的应用的更多相关文章

  1. java多线程系列(七)---Callable、Future和FutureTask

    Callable.Future和FutureTask 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量 ...

  2. Java多线程编程:Callable、Future和FutureTask浅析(多线程编程之四)

    java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)java多线程同步以及线程间通信详解&消费者生产者模式&死锁& ...

  3. Java多线程编程:Callable、Future和FutureTask浅析

    通过前面几篇的学习,我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种方式都有个缺点,那就是在任务执行完成之后无法获取返回结果,那如果我们想要获取返回结 ...

  4. JAVA多线程提高七:Callable与Future的应用

    Callable与Runnable 先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法: public interface Runnable { publ ...

  5. JAVA多线程学习十六 - 同步集合类的应用

    1.引言 在多线程的环境中,如果想要使用容器类,就需要注意所使用的容器类是否是线程安全的.在最早开始,人们一般都在使用同步容器(Vector,HashTable),其基本的原理,就是针对容器的每一个操 ...

  6. JAVA多线程学习十五 - 阻塞队列应用

    一.类相关属性 接口BlockingQueue<E>定义: public interface BlockingQueue<E> extends Queue<E> { ...

  7. JAVA多线程学习十二 - Semaphere同步工具

    java 中Semaphere可类比操作系统信号量,硬件资源如IO.内存.磁盘等都是有固定量的,多个程序需要竞争这些资源,没有资源就需要被挂起. 一.类和方法摘要 构造函数: public Semap ...

  8. 转:Java多线程学习(总结很详细!!!)

    Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢? 本文主要讲java中多线程 ...

  9. Java多线程学习笔记(一)——多线程实现和安全问题

    1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...

随机推荐

  1. <数据结构>关键路径

    目录 AOV网和AOE网 AOV网 AOE网 定义 与AOV网的转化 AOE网中着重解决的两个问题 1.最长路径问题 2.关键活动问题 总结 最长路径 无正环的图 有向无环图的最短路径 其他情况 关键 ...

  2. .Net Core 3.1 WebApi使用Swagger生成Api文档

    用swagger生成Api文档 1.安装Swashbuckle.AspNetCore 右键单击"解决方案资源管理器" > "管理 NuGet 包"中的项目 ...

  3. 初识python 之 cx_oracle

    使用cx_oracle操作oracle数据库 oracleClient 包 #!/user/bin env python # author:Simple-Sir # create_time: 2021 ...

  4. linux 之 nginx安装步骤

    配置规划 用户 lzh 用户目录  /lzh 下载 进入官网下载nginx http://nginx.org/download/ 安装 解压 cd /lzh/app tar -zxvf nginx-1 ...

  5. Flask_CSRF保护(十一)

    flask使用 flask-wtf 模块提供的 CSRFProtect对象开启CSRF防护,方法如下: 后端设置 from flask import Flask from flask_wtf.csrf ...

  6. nginx + tomcat 单个域名及多个域名的配置

    //nginx + tomcat 单个域名及多个域名的配置//修改nginx的配置文件,linux默认路径 /usr/local/nginx/conf/nginx.conf //prot为8082的w ...

  7. spring boot pom.xml 提示 ignored 具体解决

    1.出现这个情况 2. 进入设置,找到 去掉勾选即可

  8. 第10组 Beta冲刺 总结

    1.基本情况 组长博客链接:https://www.cnblogs.com/cpandbb/p/14050808.html 答辩总结: ·因为alpha阶段的产品做得偏离了方向,所以beta冲刺大家非 ...

  9. Go标准库之html/template

    html/template包实现了数据驱动的模板,用于生成可防止代码注入的安全的HTML内容.它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用html/templ ...

  10. Spark案例练习-打包提交

    关注公众号:分享电脑学习回复"百度云盘" 可以免费获取所有学习文档的代码(不定期更新)云盘目录说明:tools目录是安装包res 目录是每一个课件对应的代码和资源等doc 目录是一 ...