Runnable Callable Future FutureTask 及应用

一般创建线程只有两种方式,一种是继承Thread,一种是实现Runnable接口。但是这两种创建方式有一个致命的缺点就是没有返回值,没返回值就让人很苦恼了啊。得用共享变量或者其他通信方式才能得到线程处理完的结果,就有点麻烦。
还有一般不提倡使用继承Thread来创建线程方式,因为Java只有单继承,不能继承多个。但是Runnable是接口,所以可以让你的实现类同时实现多个接口。而且之后要上线程池,如果之前你是用Runnable来实现的,那就可以直接传入Runnable给线程池进行管理了!
在Java1.5之后就有了Callable、Future。这二种可以提供线程执行完的结果!
接下来简单介绍下Runnable、Callable 、Future、 FutureTask。

Runnable

Runnable 是一个接口,简单就一个方法,实现run方法,在run方法里面编写你要执行的代码就行了,但是没有任务返回接口,并且无法抛出异常。

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

简单应用

    Runnable runnable =
        new Runnable() {
          @Override
          public void run() {
            System.out.println("2333");
          }
        };
    Thread thread = new Thread(runnable);
    thread.start();

Callable

Callable也是一个接口,很简单就一个call方法,在call方法里面编写你要执行的代码就行了,返回的就是执行的结果了。和Runnable 差别就是它有返回的结果,并且可以抛出异常!一般配合ThreadPoolExecutor使用的。

public interface Callable<V> {
   V call() throws Exception;
}

简单应用,它不能直接配合Thread 使用。

   Callable<String> callable =
        new Callable<String>() {
          @Override
          public String call() throws Exception {
            return "2333";
          }
        };
    ExecutorService executor = Executors.newFixedThreadPool(1);
    Future<String> result = executor.submit(callable);
    System.out.println(result.get());

Future

Future也是一个接口,它可以对具体的Runnable或者Callable任务进行取消、判断任务是否已取消、查询任务是否完成、获取任务结果。如果是Runnable的话返回的结果是null(下面会剖析为什么Runnable的任务,Future还能返回结果)。接口里面有以下几个方法。注意两个get方法都会阻塞当前调用get的线程,直到返回结果或者超时才会唤醒当前的线程。

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;
}

简单应用

    Callable<String> callable =
        new Callable<String>() {
          @Override
          public String call() throws Exception {
            return "2333";
          }
        };
    ExecutorService executor = Executors.newFixedThreadPool(1);
    Future<String> result = executor.submit(callable);
    result.cancel(true);
    System.out.println(result.isCancelled());

FutureTask

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
FutureTask不是接口了,是个class。它实现了RunnableFuture接口
public class FutureTask<V> implements RunnableFuture<V>
而RunnableFuture接口又继承了Runnable和Future
public interface RunnableFuture<V> extends Runnable, Future<V>
因此它可以作为Runnable被线程执行,又可以有Future的那些操作。它的两个构造器如下

FutureTask(Callable<V> callable);
FutureTask(Runnable runnable, V result);

简单应用

FutureTask<String> futureTask = new FutureTask<>(() -> "2333");
Thread T1 = new Thread(futureTask);
T1.start();
String result = futureTask.get();
System.out.println(result);

线程池应用

一般情况我们在用多线程的时候都会上线程池,而且一般我们使用ThreadPoolExecutor来创建线程池,我的上篇文章已经讲述了ThreadPoolExecutor,这里再补充一下submit用法。

Future<?> submit(Runnable task); // 提交 Runnable 任务
<T> Future<T> submit(Callable<T> task); // 提交 Callable 任务
<T> Future<T> submit(Runnable task, T result); // 提交 Runnable 任务及结果引用  

也就是说如果我们需要返回任务的执行结果我们就得调用submit方法而不是execute。
submit也不神秘就是封装了一下我们的任务再execute

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);  //转成 RunnableFuture,传的result是null
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

newTaskFor方法是new了一个FutureTask返回。
所以三个方法其实都是把task转成FutureTask,如果task是Callable,就直接赋值。如果是Runnable 就转为Callable再赋值

task是Callable的情况

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;      
    }

task是Runnable 的情况

   // 按顺序看,层层调用
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);  //转 runnable 为 callable 
        this.state = NEW; 
    }
   // 以下为Executors中的方法
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    static final class RunnableAdapter<T> implements Callable<T> {  //适配器
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {   
            task.run();
            return result;
        }
    }

看了源码就揭开了神秘面纱了,就是因为Future需要返回结果,所以内部task必须是Callable,如果task是Runnable 我就造个假,偷天换日,在Runnable 外面包个Callable马甲,返回的结果在构造时就写好。
如果是调用Future<?> submit(Runnable task);提交任务,构造的时候就直接是RunnableFuture<Void> ftask = newTaskFor(task, null);直接塞了个null。所以调用Future.get时,拿到的就是null。所以这样有什么用呢?就只用能来判断任务已经执行完毕,就类似于Thread.join。
这还有个<T> Future<T> submit(Runnable task, T result);你可能会奇怪这有啥用啊,你传入一个result,Future.get的时候返回的不还是这个result嘛。是个没错,但是这样用就行了!

class Task implements Runnable{  //定义task,传入p
    Person p;
    Task(Person r){  // 通过构造函数传入 p
      this.p = p;
    }
    void run() {
      r.setSex("男");      // 可以修改p
    }
  }
  Person p = new Person();
  p.setName("小明");
  ExecutorService executor  = Executors.newFixedThreadPool(1);
  Future<Result> future = executor.submit(new Task(p), p);  
  Person person = future.get();
  person.getSex();

这样就可以得到修改后的结果了!

总结

综上所述,知道了Runnable 和Callable 的区别,Callable 可以获得任务结果和抛出异常,Runnable 没结果没异常抛出。
Future可以很容易的获得异步执行的结果,并且对任务进行一些操控。并且get等待结果时会阻塞,所以当任务之间有依赖关系的时候,一个任务依赖另一个任务的结果,可以用Future的get来等待依赖的任务完成的结果。
FutureTask 就是具体的实现类,有Runnable 的特性又有Future的特性,内部包的是Callable ,当然也有接受Runnable 的构造器,只是会偷偷把Runnable 转成Callable 来实现能返回结果的方法。


如果错误欢迎指正!
个人公众号:yes的练级攻略

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容