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、循环的次数小于提交任务的个数,则会返回最先完成的部分任务。
运行结果如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通