Java中的Runnable、Callable、Future、FutureTask的区别与示例

简介: Java中的Runnable、Callable、Future、FutureTask的区别与示例

Java中存在Runnable、Callable、Future、FutureTask这几个与线程相关的类或者接口,在Java中也是比较重要的几个概念,我们通过下面的简单示例来了解一下它们的作用于区别。


Runnable


其中Runnable应该是我们最熟悉的接口,它只有一个run()函数,用于将耗时操作写在其中,该函数没有返回值。然后使用某个线程去执行该runnable即可实现多线程,Thread类在调用start()函数后就是执行的是Runnable的run()函数。Runnable的声明如下

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


Callable


Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。Callable的声明如下 :

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


可以看到,这是一个泛型接口,call()函数返回的类型就是客户程序传递进来的V类型。 Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果( Future简介 )。Future声明如下 :

public interface Future<T>
{
    V get() throws ...;
    V get(long timeout, TimeUnit unit) throws ...;
    void cancle(boolean mayInterrupt);
    boolean isCancelled();
    boolean isDone();
}

具体的实现类为java.util.concurrent.FutureTask<V>。


FutureTask


FutureTask则是一个RunnableFuture<V>,而RunnableFuture实现了Runnbale又实现了Futrue<V>这两个接口,

public class FutureTask<V> implements RunnableFuture<V>

  RunnableFuture

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


另外它还可以包装Runnable和Callable<V>, 由构造函数注入依赖。

public FutureTask(Callable<V> 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
  }

可以看到,Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。该适配函数的实现如下 :

public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }


RunnableAdapter适配器

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

     由于FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行。并且还可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。


简单示例


package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
 * Runnable和Callable的区别
 * Runnable没有返回值
 * Callable有返回值,值是通过Future获得的。
 * 
 * FutureTask是一个RunnableFuture<V>,而RunnableFuture实现了Runnbale又实现了Futrue<V>这两个接口,
  可以包装Runnable和Callable<V>, 由构造函数注入依赖。
 * @author hadoop
 *
 */
public class RunnableFutureTask {
    static  ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    /**
     * 效率低下的斐波那契数列,耗时操作。这里没有引用外部共享域,fibc方法是线程安全的,不用考虑同步
     * @param num
     * @return
     */
    static int  fibc(int num){
      if(num == 0){
      return 0;
      }
      if(num == 1){
      return 1;
      }
      return  fibc(num-1)+fibc(num-2);
    }
    /**
     * Runnable,无返回值
     */
    static void runnableDemo(){
      new Thread(
         new Runnable() {
    @Override
    public void run() {
      System.out.println("Runnable demo:" + fibc(20));  
    }
    } 
      ).start();
    }
    /**
     * 其中Runnable实现的是void run()方法,无返回值;
     * Callable实现的是V call()方法,并且可以返回执行结果。
     * 其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecutorService来执行
     */
    static void futureDemo(){
      try {
       /**
        * 提交Runnable则没有返回值,futurn没有数据
        * 使用submit提交任务会返回Future对象,而使用execute没有返回值。
        * submit提交Runnable任务本质上也是转化为Callable去执行
        */
    Future<?>  result = mExecutor.submit(new Runnable() {
    @Override
    public void run() {
       fibc(20);   
    }
    });
    System.out.println("future result from runnable:" + result.get());
    /**
    * 提交Callable,有返回值,future中能够获取返回值
    */
    Future<Integer> result2 = mExecutor.submit(new Callable<Integer>(){
    @Override
    public Integer call() throws Exception {
      // TODO Auto-generated method stub
      return fibc(20);
    }
    });
    System.out.println("future result from  callable:" + result2.get());
    /**
    * FutureTask是一个RunnableFuture<V>,而RunnableFuture实现了Runnbale又实现了Futrue<V>这两个接口
    * 同时包装了Runnable和Callable<V>, 由构造函数注入依赖。
    * Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务
    */
    FutureTask<Integer> futureTask = new FutureTask<Integer>(
      new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
        // TODO Auto-generated method stub
        return fibc(20);
        }
    });
    //提交futureTask
    mExecutor.submit(futureTask);
    System.out.println("future result from futureTask:" + futureTask.get());
  } catch (InterruptedException e) {
    e.printStackTrace();
  } catch (ExecutionException e) {
            e.printStackTrace();
  }
    }
    public static void main(String[] args) {
    runnableDemo();
    futureDemo();
      System.out.println("已经开启所有的子线程");  
      mExecutor.shutdown();  
         System.out.println("shutdown():启动一次顺序关闭,执行以前提交的任务,但不接受新任务。");  
         while(true){  
             if(mExecutor.isTerminated()){  
                 System.out.println("所有的子线程都结束了!");  
                 break;  
             }  
         }  
  }
}
目录
相关文章
|
10天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
5天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
14天前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
|
14天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3
|
14天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
14天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
27 2
|
14天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2
|
11天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
90 38
|
9天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?