Bootstrap

Java多线程(十): FutureTask CompletableFuture详解

1. Runnable与Callable

RunnableCallable
方法run()call()
返回值void传入的泛型,推荐使用带超时参数的get方法获取
异常内部处理可向上抛
使用new Thread(Runnable target)配合ExecutorService的submit方法

2. Future接口和FutureTask

  • FutureTask实现Future接口
  • Java8的CompletableFuture实现Future接口
  • 异步运算,它有启动和取消运算、查询运算是否完成和取回运算结果等方法
  • 传入Callable的任务给FutureTask
  • get方法将会阻塞

3. ExecutorService几种线程池

线程池作用
CachedThreadPool一个无限扩大可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。比较适合处理执行大量的短期小任务。
FixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
SingleThreadExecutor一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
WorkStealingPool会根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层用的ForkJoinPool来实现的。可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

4. 使用Callable+FutureTask获取执行结果

public class TheTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("Task is running!");
        Thread.sleep(2000);
        int sum = 0;
        for (int i=0; i< 10; i++){
            sum += i;
        }
        return sum;
    }
}
public class TestFutureTaskAndExecutor {

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        TheTask task = new TheTask();
        Future<Integer> result = pool.submit(task);
        try {
            System.out.println("result is " + result.get(5, TimeUnit.DAYS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
}

5. CompletableFuture

5.1 CompletableFuture中4个异步执行任务静态方法

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {
    return asyncSupplyStage(screenExecutor(executor), supplier);
}

public static CompletableFuture<Void> runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
}

public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
    return asyncRunStage(screenExecutor(executor), runnable);
}

supplyAsync用于有返回值的任务,runAsync则用于没有返回值的任务。Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池

CompletableFuture,默认依靠fork/join框架启动新的线程实现异步与并发的。

它提供了函数式编程的能力,可以通过回调函数的方式处理返回结果,并且提供了转换和组合CompletableFuture的方法。

在用CompletableFuture之前,我认为得先熟悉下Java8的Lambda编程语法,《Java8实战》这本书非常适合阅读。

CompletableFuture创建线程有2种方式:supplyAsync(有返回值)和:runAsync(无返回值)

5.2 创建测试用的业务类

创建一个DeptService,模拟根据Id获取部门的方法getById(Integer id)。

public class DeptService {

    public Dept getById(Integer id)  {

        System.out.println("线程:" + Thread.currentThread().getName() + " getById(" + id + ")");

        if (id == 1){
            return new Dept(1, "研发一部");
        } else if (id == 2){
            return new Dept(2, "研发二部");
        } else {
            throw null;
        }
    }
}

创建一个UserService ,模拟getById()和save()这2个方法。

public class UserService {

	//根据Id获取User
    public User getById(Integer id) throws Exception {

        System.out.println("线程:" + Thread.currentThread().getName() + " getById(" + id + ")");

        if (id == 1){
            return new User(1, "冬哥", 31);
        } else if (id == 2){
            return new User(2, "珣爷", 30);
        } else {
            throw new Exception("未能找到人员");
        }
    }

	//保存User
    public User save(User user){
        System.out.println("线程:" + Thread.currentThread().getName() + " save()," + user.toString());
        return user;
    }
}

5.3 supplyAsync(有返回值)

supplyAsync有2种,第二个需要多传如线程池的实现。

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                       Executor executor)

需求:

  • 在main线程里创建一个线程异步获取id=1的部门
  • 在main线程里获取新线程的返回值

supplyAsync()中调取deptService.getById(1),并return它的值。

public class Thread01_SupplyAsync {


    public static void main(String[] args) throws ExecutionException, InterruptedException {

        DeptService deptService = new DeptService();

        CompletableFuture<Dept> deptCompletableFuture = CompletableFuture.supplyAsync(() -> {

            Dept dept = deptService.getById(1);
            return dept;
        });

        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + deptCompletableFuture.get());
    }
}

运行结果如下,可见DeptService是在新的线程里执行的,而在main主线程中调用get()获取线程的结果会阻塞主线程。

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main 结果:Dept{id=1, name='研发一部'}

5.4 runAsync(无返回值)

runAsync适用无返回值的情况,也有2种,第二个需要多传如线程池的实现

public static CompletableFuture<Void> runAsync(Runnable runnable)

public static CompletableFuture<Void> runAsync(Runnable runnable,
                                                   Executor executor)

对上面SupplyAsync的测试代码稍做修改,runAsync()里没有返回值,所以去掉return。

public class Thread02_RunAsync {
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        DeptService deptService = new DeptService();

        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {

            deptService.getById(1);
        });

        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + voidCompletableFuture.get());
    }
}

运行结果如下,可以看出runAsync也是创建了新线程,调用get方法只能返回null。

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main 结果:null

6. 串行的回调函数

当子线程完成后,需要调用一些回调方法,如果是Java8以前,我们写的会稍微复杂。

Java8的CompletableFuture已经为我们实现了几个回调函数,使用非常方便。

1. thenApply 转换结果

apply有“申请”、“应用”的意思,我个人理解为把上一个线程的结果“应用于”下一个线程的计算。相当于结果值的传递。

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

其中thenApply是同步的,thenApplyAsync是异步的。

Function<? super T,? extends U>

T:上一个任务返回结果的类型
U:当前任务的返回值类型

需求:

  • 在main线程里创建一个线程异步获取id=1的部门
  • 将上面线程的返回值传递给下一个任务:给user赋值部门信息,并保存user
  • 在main线程获取保存后user的值
public class Thread03_SupplyAsync_ThenApply {
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        DeptService deptService = new DeptService();
        UserService userService = new UserService();

        User user = new User(1, "冬哥", 31);

        CompletableFuture<User> userCompletableFuture = CompletableFuture.supplyAsync(() -> {

            Dept dept = deptService.getById(1);
            return dept;
        })
                .thenApplyAsync(dept -> {

                    //注意这里用到了上个线程的返回值dept
                    user.setDeptId(dept.getId());
                    user.setDeptName(dept.getName());

                    return userService.save(user);
                });


        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + userCompletableFuture.get().toString());
    }
}

运行结果如下:

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:ForkJoinPool.commonPool-worker-1 save(),User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
线程:main 结果:User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}

如果将thenApplyAsync()替换成thenApply(),第二个任务将在主线程中同步执行,结果如下:

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main save(),User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
线程:main 结果:User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}

2. thenAccept 消费结果

thenAccept 同 thenApply 接收上一个任务的返回值作为参数,但是回调方法无返回值

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

需求:

  • 在main线程里创建一个线程异步获取id=1的部门
  • 将上面线程的返回值dept传递给下一个任务:dept作为日志记录发给Kafka
public class Thread04_SupplyAsync_ThenAccept {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        DeptService deptService = new DeptService();

        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {

            Dept dept = deptService.getById(1);
            return dept;
        })
                .thenAcceptAsync(dept -> {

                    //注意这里用到了上个线程的返回值dept
                    System.out.println("线程:" + Thread.currentThread().getName() +
                            "假设把dept作为日志记录发给Kafka: " + dept.toString());
                    //thenAccept是没有返回值的
                });

        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + voidCompletableFuture.get());
    }
}

运行结果如下:

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:ForkJoinPool.commonPool-worker-1把dept作为日志记录发给Kafka: Dept{id=1, name='研发一部'}
线程:main 结果:null

如果thenAcceptAsync(异步)改成thenAccept(同步),结果如下:

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main把dept作为日志记录发给Kafka: Dept{id=1, name='研发一部'}
线程:main 结果:null

3. thenRun 任务完成后触发的回调

thenRun 是上一个任务完成后触发的回调,没有入参,也没有返回值。

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

需求:

  • 在main线程里创建一个线程异步获取id=1的部门
  • 上面线程结束后,执行thenRun里的回调,没有入参和返回值
public class Thread05_SupplyAsync_ThenRun {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        DeptService deptService = new DeptService();

        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {

            Dept dept = deptService.getById(1);
            return dept;
        })
                .thenRun(() -> {//注意没有入参

                    System.out.println("线程:" + Thread.currentThread().getName() + " do something");
                    //thenRun注意没有入参,也没有返回值
                });

        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + voidCompletableFuture.get());
    }
}

运行结果如下:

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main do something
线程:main 结果:null

4. thenCompose

thenCompose 可以用于组合多个CompletableFuture,将前一个任务的返回结果作为下一个任务的参数,它们之间存在着先后顺序。

thenCompose方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例,如果该CompletableFuture实例的result不为null,则返回一个基于该result的新的CompletableFuture实例;如果该CompletableFuture实例为null,则,然后执行这个新任务。

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;

thenApply()和thenCompose()的区别:
thenApply()转换的是泛型中的类型,是同一个CompletableFuture,相当于将CompletableFuture 转换成CompletableFuture

thenCompose()用来连接两个CompletableFuture,是生成一个新的CompletableFuture。

下面对thenApply的测试代码进行修改,注意下图中区别
在这里插入图片描述

  • thenCompose生成了一个新的CompletableFuture
  • thenApply还是原来的CompletableFuture,只是泛型从Dept转换成User。
public class Thread06_SupplyAsync_ThenCompose {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        DeptService deptService = new DeptService();
        UserService userService = new UserService();

        User user = new User(1, "冬哥", 31);

        CompletableFuture<User> userCompletableFuture = CompletableFuture.supplyAsync(() -> {

            Dept dept = deptService.getById(1);
            return dept;
        })
                .thenCompose(dept -> CompletableFuture.supplyAsync(() -> {
                    //注意这里用到了上个线程的返回值dept
                    user.setDeptId(dept.getId());
                    user.setDeptName(dept.getName());

                    return userService.save(user);
                }));
        
        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + userCompletableFuture.get().toString());
    }
}

运行结果如下:

线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:ForkJoinPool.commonPool-worker-1 save(),User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
线程:main 结果:User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}

7. thenApply,thenAccept,thenRun,thenCompose的区别

thenApply,thenAccept,thenRun,thenCompose的区别如下:

特点thenApplythenAcceptthenRunthenCompose
入参
返回值
新的CompletableFuture

8. 组合方法

allOfanyOf
等待所有任务完成,构造后CompletableFuture完成只要有一个任务完成,构造后CompletableFuture就完成
/**
     * 使用定制的 Executor 配置 CompletableFuture
     *
     * @param product
     * @return
     */
    public List<String> findPrice4(String product) {

        //为“最优价格查询器”应用定制的执行器 Execotor
        Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        //使用守护线程,使用这种方式不会组织程序的关停
                        thread.setDaemon(true);
                        return thread;
                    }
                }
        );

      //将执行器Execotor 作为第二个参数传递给 supplyAsync 工厂方法
        List<CompletableFuture<String>> futures = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(
                        () -> String.format("%s price is %.2f RMB",
                                shop.getName(),
                                shop.getPrice(product)), executor)
                )
                .collect(toList());
        List<String> list = futures.stream()
                .map(CompletableFuture::join)
                .collect(toList());


        return list;
    }
;