Future初探

1、Runable和Callable

Java里面实现多线程的三种方式:一、是继承Thread类;二、实现Runable接口;三、实现Callable接口。

第一种方式继承Thread,然后重写run();

第二种方式实现Runable接口,然后实现run();

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

第三种方法是实现Callable接口,然后实现call()。

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

 

上图可以看出,Runable和Callable的最直观的区别就是Runable没有返回值,而Callable是有返回值的。利用返回值我们可以干很多事情。

那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

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

1、第一个方法submit提交了一个实现Callable接口的任务,返回封装了异步计算结果的Future。

2、第二个方法submit提交了一个实现Runable接口的任务,并且指定了在调用Future的get方法时返回的result对象。

3、第三个方法submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。

我们只需要创建好实现Runable接口或Callable接口的线程对象,然后提交到线程池执行就可以了。

2、Future

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

Future有五个方法可供使用,如下所示:

boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get();
V get(long timeout, TimeUnit unit);

1、用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务。

  • 如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;
  • 如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;
  • 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true

2、任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

3、任务是否已经完成,若任务完成,则返回true。

4、用于获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回。

5、用来获取执行结果,如果在指定时间内还没有获取到结果,则抛出TimeoutException异常。

总的来说,Future提供了三种功能:

  • 判断任务是否完成;
  • 取消正在任务;
  • 获取任务执行结果;

3、Future使用例子

1、使用场景:需要并行的执行一些任务,且这些任务全部执行完成后,才能进行后续操作。

public class TestFuture {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws Exception {
List<Future<String>> futureList = new ArrayList<>();
for (int i = 1; i < 10; i++) {
int finalI = i;
Future<String> future = executorService.submit(() -> {
String threadName = Thread.currentThread().getName();
Thread.sleep(1000 * (10 - finalI));
return threadName;
});
futureList.add(future);
}
executorService.shutdown();
List<String> list = new ArrayList<>();
for (int i = 0; i < futureList.size(); i++) {
String name = futureList.get(i).get();
list.add(name);
System.out.println(name);
}
System.out.println(JsonUtil.toJson(list));
}
}
  • 创建一个线程池
  • 创建一个List存储返回的Future结果
  • 使用第一节说的submit提交异步任务,每个任务执行时间不相同
  • 将返回的Future结果放入List
  • 循环遍历返回结果,直到所有任务执行完成

注意:

1、这里是按任务的添加顺序获取结果,即按任务添加顺序向下执行,与每个任务的执行时间无关,哪怕后面的任务先执行完成也是后面才会获取结果。

2、如果线程池的最大线程数量小于任务数量,则会分批进行任务处理。

执行结果:

2、使用场景:需要并行的执行一些任务,哪个任务先执行完成则先进行处理。

方法一:轮询futureList获取结果,下面给出例子:

public class TestFuture {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
List<Future<String>> futureList = new ArrayList<>();
for (int i = 1; i < 10; i++) {
int finalI = i;
Future<String> future = executorService.submit(() -> {
String threadName = Thread.currentThread().getName();
Thread.sleep(1000 * (10-finalI));
return threadName;
});
futureList.add(future);
}
executorService.shutdown();
while (futureList.size() != 0) {
for (int i = 0; i < futureList.size(); i++) {
String name = null;
try {
name = futureList.get(i).get(0, TimeUnit.SECONDS);
} catch (Exception e) {
}
if (name != null) {
System.out.println(name);
futureList.remove(i);
break;
}
}
}
}
}
  • 轮询futureList获取每个任务执行情况
  • 设置超时时间为0,反复去get结果
  • 上面说过超过设置的时间未获取到结果则会抛出TimeoutException,所以针对每个get方法需要捕获异常,保证能继续轮询下去。
  • 获取到结果后,则从futureList中移除掉,当所有的任务执行完成后,则结束轮询。

总结:此种方式可以按任务执行完成的顺序进行后续操作,同理也可以获取最先执行完成的任务。

先加入的任务时间是最长的,所以执行结果:

方法二:CompletionService实现

CompletionService:将产生异步任务和消费已完成任务进行解耦,生产者和消费者不用关心任务的完成顺序,由CompletionService来保证,消费者一定是按照任务完成的先后顺序来获取执行结果。

CompletionService提供了五个方法:

Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
Future<V> take() throws InterruptedException;
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit);

1、第一个方法submit提交了一个实现Callable接口的任务,返回封装了异步计算结果的Future。

2、第二个方法submit提交了一个实现Runable接口的任务,并且指定了在调用Future的get方法时返回的result对象。

3、take()获取任务阻塞,直到可以拿到任务为止。

4、poll()获取任务不阻塞,如果没有获取到任务直接返回null。

5、pool(long timeout, TimeUnit unit)带超时时间等待的获取任务方法。

下面是采用CompletionService实现的例子:

public class TestFuture {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws Exception {
CompletionService completionService = new ExecutorCompletionService(executorService);
for (int i = 1; i < 10; i++) {
int finalI = i;
completionService.submit(() -> {
String threadName = Thread.currentThread().getName();
Thread.sleep(1000 * (10 - finalI));
return threadName;
});
}
List<String> list = new ArrayList<>();
for (int i = 0; i < 12; i++) {
Future o = completionService.poll(14, TimeUnit.SECONDS);
if (ObjectUtils.isEmpty(o)) {
break;
}
String name = (String) o.get();
System.out.println(name);
list.add(name);
}
System.out.println(JsonUtil.toJson(list));
}
}

注意:1、当循环的次数大于提交的任务个数,如果采用take()的方式来获取结果,由于take()是阻塞的,此时会一直停滞等待获取结果。例子中使用的是poll(long timeout, TimeUnit unit),设置超时时间,如果超过设定时间则返回null

,此时可以跳出循环。所以推荐使用poll(long timeout, TimeUnit unit),防止主线程阻塞。

2、循环的次数等于提交任务个数,则会返回返回所有任务的结果。

3、循环的次数小于提交任务的个数,则会返回最先完成的部分任务。

运行结果如下:

posted @   浪迹天涯的派大星  阅读(103)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示