Java多线程-Callable的Future返回值的使用

 

通常使用线程池执行任务都是调用的execute方法,这个方法定义在Executor接口中:ide

public interface Executor {   void execute(Runnable command); }

这个方法是没有返回值的,并且只接受Runnable。this

那么像获得线程的返回值怎嘛办呢?spa

在ExecutorService接口中能找到这个方法:线程

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

这个方法接收两种参数,Callable和Runnable。返回值是Future。日志

下面具体看一下这些是什么东西。code

 

Callable和Runnable
先看一下两个接口的定义:orm

Callableblog

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

Runnable接口

interface Runnable {   public abstract void run(); }


和明显能看到区别:get

1.Callable能接受一个泛型,而后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
2.Callable的call方法能够抛出异常,而Runnable的run方法不会抛出异常。


Future
返回值Future也是一个接口,经过他能够得到任务执行的返回值。

定义以下:

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

其中的get方法获取的就是返回值。

来个例子

submit(Callable task)

public class Main {   public static void main(String[] args) throws InterruptedException, ExecutionException {   ExecutorService executor = Executors.newFixedThreadPool(2);   //建立一个Callable,3秒后返回String类型
  Callable myCallable = new Callable() {     @Override     public String call() throws Exception {       Thread.sleep(3000);       System.out.println("calld方法执行了");       return "call方法返回值";     }   };   System.out.println("提交任务以前 "+getStringDate());   Future future = executor.submit(myCallable);   System.out.println("提交任务以后,获取结果以前 "+getStringDate());   System.out.println("获取返回值: "+future.get());   System.out.println("获取到结果以后 "+getStringDate());   }   public static String getStringDate() {     Date currentTime = new Date();     SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");     String dateString = formatter.format(currentTime);     return dateString;     }   }

经过executor.submit提交一个Callable,返回一个Future,而后经过这个Future的get方法取得返回值。

看一下输出:

提交任务以前 12:13:01
提交任务以后,获取结果以前 12:13:01
calld方法执行了
获取返回值: call方法返回值
获取到结果以后 12:13:04


get()方法的阻塞性
经过上面的输出能够看到,在调用submit提交任务以后,主线程原本是继续运行了。可是运行到future.get()的时候就阻塞住了,一直等到任务执行完毕,拿到了返回的返回值,主线程才会继续运行。

这里注意一下,他的阻塞性是由于调用get()方法时,任务尚未执行完,因此会一直等到任务完成,造成了阻塞。

任务是在调用submit方法时就开始执行了,若是在调用get()方法时,任务已经执行完毕,那么就不会形成阻塞。

下面在调用方法前先睡4秒,这时就能立刻获得返回值。

System.out.println("提交任务以前 "+getStringDate());
Future future = executor.submit(myCallable);
System.out.println("提交任务以后 "+getStringDate());
Thread.sleep(4000);
System.out.println("已经睡了4秒,开始获取结果 "+getStringDate());
System.out.println("获取返回值: "+future.get());
System.out.println("获取到结果以后 "+getStringDate());
提交任务以前 12:36:04
提交任务以后 12:36:04
calld方法执行了
已经睡了4秒,开始获取结果 12:36:08
获取返回值: call方法返回值
获取到结果以后 12:36:08

能够看到吗,由于睡了4秒,任务已经执行完毕,因此get方法立马就获得告终果。

一样的缘由,submit两个任务时,总阻塞时间是最长的那个。

例如,有两个任务,一个3秒,一个5秒。

Callable myCallable = new Callable() {
  @Override
  public String call() throws Exception {
  Thread.sleep(5000);
  System.out.println("calld方法执行了");
  return "call方法返回值";
  }
};
Callable myCallable2 = new Callable() {
  @Override
  public String call() throws Exception {
  Thread.sleep(3000);
  System.out.println("calld2方法执行了");
  return "call2方法返回值";
  }
};
System.out.println("提交任务以前 "+getStringDate());
Future future = executor.submit(myCallable);
Future future2 = executor.submit(myCallable2);
System.out.println("提交任务以后 "+getStringDate());
System.out.println("开始获取第一个返回值 "+getStringDate());
System.out.println("获取返回值: "+future.get());
System.out.println("获取第一个返回值结束,开始获取第二个返回值 "+getStringDate());
System.out.println("获取返回值2: "+future2.get());
System.out.println("获取第二个返回值结束 "+getStringDate());

输出

提交任务以前 14:14:47
提交任务以后 14:14:48
开始获取第一个返回值 14:14:48
calld2方法执行了
calld方法执行了
获取返回值: call方法返回值
获取第一个返回值结束,开始获取第二个返回值 14:14:53
获取返回值2: call2方法返回值
获取第二个返回值结束 14:14:53

获取第一个结果阻塞了5秒,因此获取第二个结果立马就获得了。

submit(Runnable task)
由于Runnable是没有返回值的,因此若是submit一个Runnable的话,get获得的为null:

Runnable myRunnable = new Runnable() {
  @Override
  public void run() {
  try {
    Thread.sleep(2000);
    System.out.println(Thread.currentThread().getName() + " run time: " + System.currentTimeMillis());
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  }
};

Future future = executor.submit(myRunnable);
System.out.println("获取的返回值: "+future.get());

输出为:

pool-1-thread-1 run time: 1493966762524
获取的返回值: null

 

submit(Runnable task, T result)
虽然submit传入Runnable不能直接返回内容,可是能够经过submit(Runnable task, T result)传入一个载体,经过这个载体获取返回值。这个其实不能算返回值了,是交给线程处理一下。

先新建一个载体类Data:

public static class Data {
  String name;
  String sex;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getSex() {
    return sex;
  }

  public void setSex(String sex) {
    this.sex = sex;
  }
}

而后在Runnable的构造方法中传入:

static class MyThread implements Runnable {
  Data data;

  public MyThread(Data name) {
    this.data = name;
  }

  @Override
  public void run() {
    try {
      Thread.sleep(2000);
      System.out.println("线程 执行:");
      data.setName("新名字");
      data.setSex("新性别");
   } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

 

而后调用:

Data data = new Data();
Future<Data> future = executor.submit(new MyThread(data), data);
System.out.println("返回的结果 name: " + future.get().getName()+", sex: "+future.get().getSex());
System.out.println("原来的Data name: " + data.getName()+", sex: "+data.getSex());


输出:

线程 执行:
返回的结果 name: 新名字, sex: 新性别
原来的Data name: 新名字, sex: 新性别

发现原来的data也变了。

 

get(long var1, TimeUnit var3)
前面都是用的get()方法获取返回值,那么由于这个方法是阻塞的,有时须要等好久。因此有时候须要设置超时时间。

get(long var1, TimeUnit var3)这个方法就是设置等待时间的。

以下面的任务须要5秒才能返回结果:

Callable myCallable = new Callable() {
  @Override
  public String call() throws Exception {
    Thread.sleep(5000);
    return "我是结果";
  }
};


使用get:

Future future1 = executor.submit(myCallable);
System.out.println("开始拿结果 "+getStringDate());
System.out.println("返回的结果是: "+future1.get()+ " "+getStringDate());
System.out.println("结束拿结果 "+getStringDate());

输出是:

开始拿结果 16:00:43
返回的结果是: 我是结果 16:00:48
结束拿结果 16:00:48

如今要求最多等3秒,拿不到返回值就不要了,因此用get(long var1, TimeUnit var3)这个方法

方法的第一个参数是长整形数字,第二个参数是单位,跟线程池ThreadPoolExecutor的构造方法里同样的。

Future future1 = executor.submit(myCallable);
System.out.println("开始拿结果 "+getStringDate());
try {
  System.out.println("返回的结果是: "+future1.get(3, TimeUnit.SECONDS)+ " "+getStringDate());
} catch (TimeoutException e) {
  e.printStackTrace();
  System.out.println("超时了 "+getStringDate());
}
System.out.println("结束拿结果 "+getStringDate());

而后输出是

 

过了三秒就抛出超时异常了,主线程继续运行,不会再继续阻塞。

 

异常
使用submit方法还有一个特色就是,他的异常能够在主线程中catch到。

而使用execute方法执行任务是捕捉不到异常的。

用下面这个Runnable来讲,这个 里面必定会抛出一个异常

Runnable myRunnable = new Runnable() {
  @Override
  public void run() {
    executor.execute(null);
  }
};


使用execute
这里若是捕捉到异常,只打印一行异常信息。

try {
  executor.execute(myRunnable);
} catch (Exception e) {
  e.printStackTrace();
  System.out.println("抓到异常 "+e.getMessage());
}

输出

 

并无出现抓到异常哪行日志。并且这个异常输出是在线程pool-1-thread-1中,并非在主线程中。说明主线程的catch不能捕捉到这个异常。

使用submit

try {
  Future future1= executor.submit(myCallable);
  future1.get();
} catch (Exception e) {
  e.printStackTrace();
System.out.println("抓到异常 "+e.getMessage());
}

输出

 

这个就能抓到异常了。

相关文章
相关标签/搜索