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、循环的次数小于提交任务的个数,则会返回最先完成的部分任务。
运行结果如下:

                    
                
                
            
        
浙公网安备 33010602011771号