Java中Runnable、Callable、Future的介绍

本文主要是为了介绍多线程中使用的几种任务:Runnable、Callable、FutureTask等,是对前面多线程系列的最后一个补充了,接下来两篇就是相当于实战练习了。

Runnable 和 Callable的区别

Runnable和Callable都是定义了接口,可以用在线程池中异步执行,区别是:

  • Runnable可以直接被Thread执行,但是没有返回值
  • Callable执行之后有返回值,但是只能提交给线程池执行。

Future 和 FutureTask

Future是一个接口,主要是线程池中任务执行之后用于返回结果的获取,定义了

  • boolean cancel(boolean mayInterruptIfRunning); 取消任务
  • boolean isCancelled(); 任务是否取消
  • boolean isDone(); 任务是否执行完毕
  • V get(); 获取任务执行的结果,注意这个方法阻塞线程
  • V get(long timeout, TimeUnit unit); 同上,只是增加了一个超时时间

Future有一个直接继承接口RunnableFuture,RunnableFuture有一个实现的子类FutureTask,RunnableFuture这个接口同时还继承了Runnable接口,这意味着FutureTask可以作为Future或者Runnable使用。

再来看一下FutureTask的实现,最终内部保存了一个Callable对象,也就是提交的任务

先看构造函数

public FutureTask(Callable<V> callable) {                    
    if (callable == null)                                    
        throw new NullPointerException();                    
    this.callable = callable;                                
    this.state = NEW;       // ensure visibility of callable 
}                                                            

...

/**                                                                    
 * Creates a {@code FutureTask} that will, upon running, execute the   
 * given {@code Runnable}, and arrange that {@code get} will return the
 * given result on successful completion.                              
 *                                                                     
 * @param runnable the runnable task                                   
 * @param result the result to return on successful completion. If     
 * you don't need a particular result, consider using                  
 * constructions of the form:                                          
 * {@code Future<?> f = new FutureTask<Void>(runnable, null)}          
 * @throws NullPointerException if the runnable is null                
 */                                                                    
public FutureTask(Runnable runnable, V result) {                       
    this.callable = Executors.callable(runnable, result);              
    this.state = NEW;       // ensure visibility of callable           
}                                                                      

一共2个构造函数,一个是接受Callable,一个是接受Runnable和默认返回值。

详细看一下第二个构造参数,注释很清楚的说明,当你需要runnable可取消同时不关心返回值时,可以这样构建

Future<?> f = new FutureTask<Void>(runnable, null);

同时构造函数里,Runnable被适配成了一个Callable,看一下里面的实现:

/**                                                                
 * Returns a {@link Callable} object that, when                    
 * called, runs the given task and returns the given result.  This 
 * can be useful when applying methods requiring a                 
 * {@code Callable} to an otherwise resultless action.             
 * @param task the task to run                                     
 * @param result the result to return                              
 * @param <T> the type of the result                               
 * @return a callable object                                       
 * @throws NullPointerException if task null                       
 */                                                                
public static <T> Callable<T> callable(Runnable task, T result) {  
    if (task == null)                                              
        throw new NullPointerException();                          
    return new RunnableAdapter<T>(task, result);                   
}     

...

/**                                                            
 * A callable that runs given task and returns given 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;                                         
    }                                                          
}                                                              
                                                             

上面两个函数将一个Runnable适配成了一个Callable,是Executors中提供的静态方法。

再看一下FutureTask对Runnable的实现

public void run() {                                                    
    if (state != NEW ||                                                
        !UNSAFE.compareAndSwapObject(this, runnerOffset,               
                                     null, Thread.currentThread()))    
        return;                                                        
    try {                                                              
        Callable<V> c = callable;                                      
        if (c != null && state == NEW) {                               
            V result;                                                  
            boolean ran;                                               
            try {                                                      
                result = c.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);                    
    }                                                                  
}                                                                      

抛开其他的判断条件,其实就是对内部保存的Callable调用了call方法,进行执行并保存结果。这就是FutureTask主要的几个方法,下面有用。

ExecutorService中Future的应用

上面2点主要是为了给这点做伏笔,现在我们来看为什么ExecutorService中的submit()既可以提交Runnable又可以提交Callable并返回结果,同时看看直接execute() Runnable会有什么不同。

Future submit(Runnable task)

先来看一下这个方法的实现

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

代码上可以很直观的看到,提交的Runnable被newTaskFor()适配成了RunnableFuture。来看一下newTaskFor()这个方法的实现。

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { 
    return new FutureTask<T>(runnable, value);                           
}                                                                        

直接是new了一个FutureTask对象,上面我们分析过这种情况,runnable其实是会被适配成一个Callable的。

Future<T> submit(Callable<T> task)

再来看一下这个方法

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

跟上面的代码简直一摸一样,都是适配成了RunnableFuture。

看到这里可以明白,提交Runnable时是将Runnable适配成了Callable,也就是submit方法最终都会调用的的是Callable对象。

上面我们说过RunnableFuture实现了Runnable接口,当他被execute时,肯定是被当作Runnable使用的,看一下两个submit方法最终都是通过execute来执行的。

上面介绍FutureTask时我们知道,对Runnable的实现FutureTask最后调用的是Callable的call方法。

到这里可以知道了,

  1. 当我们提交一个Runnable的任务时,首先通过FutureTask的构造函数被适配成了一个Callable对象被保存FutureTask中。
  2. 当任务被执行时,FutureTask又被当作一个Runnable使用,调用了保存在内部的Callable的call方法,任务被执行并返回了结果。
  3. Runnable被适配成Callable时最终调用的还是自己的run方法。

ExecutorService中execute()

public void execute(Runnable command) {                             
    if (command == null)                                            
        throw new NullPointerException();                           
    /*                                                              
     * Proceed in 3 steps:                                          
     *                                                              
     * 1. If fewer than corePoolSize threads are running, try to    
     * start a new thread with the given command as its first       
     * task.  The call to addWorker atomically checks runState and  
     * workerCount, and so prevents false alarms that would add     
     * threads when it shouldn't, by returning false.               
     *                                                              
     * 2. If a task can be successfully queued, then we still need  
     * to double-check whether we should have added a thread        
     * (because existing ones died since last checking) or that     
     * the pool shut down since entry into this method. So we       
     * recheck state and if necessary roll back the enqueuing if    
     * stopped, or start a new thread if there are none.            
     *                                                              
     * 3. If we cannot queue task, then we try to add a new         
     * thread.  If it fails, we know we are shut down or saturated  
     * and so reject the task.                                      
     */                                                             
    int c = ctl.get();                                              
    if (workerCountOf(c) < corePoolSize) {                          
        if (addWorker(command, true))                               
            return;                                                 
        c = ctl.get();                                              
    }                                                               
    if (isRunning(c) && workQueue.offer(command)) {                 
        int recheck = ctl.get();                                    
        if (! isRunning(recheck) && remove(command))                
            reject(command);                                        
        else if (workerCountOf(recheck) == 0)                       
            addWorker(null, false);                                 
    }                                                               
    else if (!addWorker(command, false))                            
        reject(command);                                            
}                                                                   

上面注释的意思是:

  1. 当前核心线程数少于corePoolSize是,尝试直接新建Thread用来执行任务。同时校验添加的过程,防止出错。
  2. 任务入队时二次校验是否需要新建线程,判断是否需要回滚等。
  3. 如果任务不能入队则新建非核心线程处理,如果失败那么就拒绝任务。

这个就是任务具体执行的过程,同时也可以知道为什么上一篇博客中通过反射获取workers的size就能知道当前线程的数量。

总结

又到总结时间,博文主要讲了几个概念Runnable、Callable、Future以及相关的子类,总结如下:

  • Runnable可以直接被Thread执行,但是没有返回值
  • Callable执行之后有返回值,但是只能提交给线程池执行。
  • Future定义了一系列关于任务取消的接口方法
  • FutureTask是Future唯一实现类,它也实现了Runnable接口
  • 线程池submit Callable和Runnable时最终都会转换成FutureTask
  • FutureTask被执行时是被当成Runnable使用的,执行了内部保存的Callable的call方法
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容