CompletableFuture使用

820 阅读8分钟

java8新引入的异步编程方式

使用方式

一、开启异步

supplyAsync(有返回值)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行");
    	return "异步运行完成";
    });

    future.join();  // 等待异步任务完成并获取结果
}

runAsync(无返回值)

public static void main(String[] args) {
    CompletableFuture<Void> future = CompletableFuture.runAsync(()-> {
    	System.out.println("开始异步运行");
    });

    future.join();  // 等待异步任务完成
}

默认是开启的异步守护线程,如果没有调用异步结果,主线程先结束的话,异步任务未完成也会直接结束。

调用异步结果,主线程会等待异步任务完成,获取结果再继续运行。

二、连接两个异步任务

thenCompose

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenCompose(result -> CompletableFuture.supplyAsync(()->{
        System.out.println("异步任务1结果:" + result);
        System.out.println("开始异步运行2");
    	return "异步运行完成2";
    }));

    future.join();  // 获取异步结果(得到的是2的结果)
}

三、做任务的后置处理

thenApply(有返回值)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenApply(result -> {
        System.out.println("异步任务1结果:" + result);
        System.out.println("开始异步运行2");
    	return "异步运行完成2";
    ));

    future.join();  // 获取异步结果(得到的是2的结果)
}

thenAccept(无返回值)

public static void main(String[] args) {
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenAccept(result -> {
        System.out.println("异步任务1结果:" + result);
        System.out.println("开始异步运行2");
    ));

    future.join();  // 等待异步任务完成
}

thenRun(无入参,无返回值)

public static void main(String[] args) {
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenRun(() -> {
        System.out.println("开始异步运行2");
    ));

    future.join();  // 等待异步任务完成
}

四、组合处理

thenCombine(有返回值)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenCombine(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2");
    	return "异步运行完成2";
    }), (x, y) -> {
        System.out.println("异步任务1结果:" + x);
        System.out.println("异步任务2结果:" + y);
        return "返回最终结果";
    });

    future.join();  // 获取异步结果(得到的是最终的结果)
}

任务1和任务2分别开了不同的线程运行。

thenAcceptBoth(无返回值)

public static void main(String[] args) {
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenAcceptBoth(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2");
    	return "异步运行完成2";
    }), (x, y) -> {
        System.out.println("异步任务1结果:" + x);
        System.out.println("异步任务2结果:" + y);
    });

    future.join();  // 等待异步任务完成
}

runAfterBoth(无入参,无返回值)

public static void main(String[] args) {
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).runAfterBoth(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2");
    	return "异步运行完成2";
    }), () -> {
        System.out.println("开始异步运行3");
    });

    future.join();  // 等待异步任务完成
}

五、优先处理

applyToEither(有返回值)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1, 需要时间3s");
    	return "异步运行完成1";
    }).applyToEither(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2, 需要时间2s");
    	return "异步运行完成2";
    }), result -> result);

    future.join();  // 获取异步结果(先处理完的结果)
}

acceptEither(无返回值)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1, 需要时间3s");
    	return "异步运行完成1";
    }).acceptEither(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2, 需要时间2s");
    	return "异步运行完成2";
    }), result -> {
        System.out.println("处理结果:" + result);
    });

    future.join();  // 等待异步任务完成
}

runAfterEither(无入参,无返回值)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1, 需要时间3s");
    	return "异步运行完成1";
    }).runAfterEither(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2, 需要时间2s");
    	return "异步运行完成2";
    }), () -> {
        System.out.println("后续操作");
    });

    future.join();  // 等待异步任务完成
}

在第一个异步任务完成后,等待就会停止,主线程会继续执行下去。

慢的异步任务也会继续执行完成,前提是主线程还没执行完。

因为异步任务使用的是守护线程,主线程已经执行完了,异步还未执行完成将看不到后续结果,因为整个程序已经结束。

六、批量处理

allOf

public static void main(String[] args) throws Exception {
    List<CompletableFuture<Void>> completableFutures = IntStream.range(0, 10)
        .mapToObj(index -> CompletableFuture.runAsync(() -> {
            System.out.println(index + "do something ...");
        }, executorService))
        .collect(Collectors.toList());

    CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join(); // 等待异步任务全部完成
}

启动了10个异步线程去处理任务,通过CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();等待所有的异步任务完成再继续。

要获取异步任务产生的结果:

public static void main(String[] args) throws Exception {
    List<CompletableFuture<String>> completableFutures = IntStream.range(0, 10)
        .mapToObj(index -> CompletableFuture.supplyAsync(() -> {
            System.out.println(index + "do something ...");
            return index + "异步任务完成";
        }, executorService))
        .collect(Collectors.toList());

    CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]))
        .thenRun(() -> {
             completableFutures.stream().map(CompletableFuture::join).forEach(System.out::println);
        });  // 等所有的任务完成后,再遍历获取处理所有的结果。
}

anyOf

public static void main(String[] args) throws Exception {
    List<CompletableFuture<Void>> completableFutures = IntStream.range(0, 10)
        .mapToObj(index -> CompletableFuture.runAsync(() -> {
            System.out.println(index + "do something ...");
        }, executorService))
        .collect(Collectors.toList());

    CompletableFuture.anyOf(completableFutures.toArray(new CompletableFuture[0])).join(); // 等待第一个异步任务完成,并获取结果
}

多个异步任务一起执行,等待第一个任务完成,并获取结果。

主程序会在得到第一个任务结果后马上往下执行,其他的异步任务不会停止,还会继续执行完。(前提是主线程还没执行完成)

七、异常处理

exceptionally(只接收异常,返回备用方案)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1, 需要时间3s");
    	return "异步运行完成1";
    }).applyToEither(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2, 需要时间2s");
    	return "异步运行完成2";
    }), result -> result).exceptionally(e -> {
        System.out.println("异常处理");
        return "备用方案";
    });

    future.join();  // 获取异步结果(出现异常了,返回备用方案)
}

handle(接收异常和结果,返回最终方案)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1, 需要时间3s");
    	return "异步运行完成1";
    }).applyToEither(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2, 需要时间2s");
    	return "异步运行完成2";
    }), result -> result).handle((e, s) -> {
        if(e != null) {
            System.out.println("异常处理:" + e.getMessage());
        }
        System.out.println("未出异常, 结果:" + s);
        return "最终方案";
    });

    future.join();  // 获取异步结果(返回最终方案)
}

whenComplete(接收异常和结果,无返回值)

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1, 需要时间3s");
    	return "异步运行完成1";
    }).applyToEither(CompletableFuture.supplyAsync(()->{
        System.out.println("开始异步运行2, 需要时间2s");
    	return "异步运行完成2";
    }), result -> result).whenComplete((s, e) -> {
        if(e != null) {
            System.out.println("异常处理:" + e.getMessage());
        }
        System.out.println("未出异常, 结果:" + s);
    });

    future.join();  // 获取异步结果(whenComplete不影响异步结果)
}

八、xxx和xxxAsync(e: thenApply() 和thenApplyAsync())

xxx

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenApply(result -> {
        System.out.println("异步任务1结果:" + result);
        System.out.println("开始异步运行2");
    	return "异步运行完成2";
    ));

    future.join();  // 获取异步结果(得到的是2的结果)
}

另起一个线程,先完成异步任务1,再完成异步任务2

xxxAsync

public static void main(String[] args) {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> {
    	System.out.println("开始异步运行1");
    	return "异步运行完成1";
    }).thenApplyAsync(result -> {
        System.out.println("异步任务1结果:" + result);
        System.out.println("开始异步运行2");
    	return "异步运行完成2";
    ));

    future.join();  // 获取异步结果(得到的是2的结果)
}

另起一个线程,完成异步任务1,再起一个线程完成异步任务2

九、线程池

默认线程池

前文一直在说,线程是守护线程,这里讲讲是为什么。(下面会出现大量源码)

asyncPool默认线程池

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

默认线程池的创建取决于useCommonPool参数

private static final Executor asyncPool = useCommonPool ? 
    ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

useCommonPool参数又取决于ForkJoinPool的commonPoolParallelism值

private static final boolean useCommonPool =
    (ForkJoinPool.getCommonPoolParallelism() > 1);

java.util.concurrent.ForkJoinPool类中有段静态方法

static {
    // initialize field offsets for CAS etc
    ......

    common = java.security.AccessController.doPrivileged
        (new java.security.PrivilegedAction<ForkJoinPool>() {
            public ForkJoinPool run() { return makeCommonPool(); }});
    int par = common.config & SMASK; // report 1 even if threads disabled
    commonParallelism = par > 0 ? par : 1;
}

commonParallelism的值和common.config有关

common是啥就要看看makeCommonPool()了

private static ForkJoinPool makeCommonPool() {

    final ForkJoinWorkerThreadFactory commonPoolForkJoinWorkerThreadFactory =
        new CommonPoolForkJoinWorkerThreadFactory();
    int parallelism = -1;
    ForkJoinWorkerThreadFactory factory = null;
    UncaughtExceptionHandler handler = null;
    try {  // ignore exceptions in accessing/parsing properties
        String pp = System.getProperty
            ("java.util.concurrent.ForkJoinPool.common.parallelism");
        String fp = System.getProperty
            ("java.util.concurrent.ForkJoinPool.common.threadFactory");
        String hp = System.getProperty
            ("java.util.concurrent.ForkJoinPool.common.exceptionHandler");
        if (pp != null)
            parallelism = Integer.parseInt(pp);
        if (fp != null)
            factory = ((ForkJoinWorkerThreadFactory)ClassLoader.
                       getSystemClassLoader().loadClass(fp).newInstance());
        if (hp != null)
            handler = ((UncaughtExceptionHandler)ClassLoader.
                       getSystemClassLoader().loadClass(hp).newInstance());
    } catch (Exception ignore) {
    }
    if (factory == null) {
        if (System.getSecurityManager() == null)
            factory = commonPoolForkJoinWorkerThreadFactory;
        else // use security-managed default
            factory = new InnocuousForkJoinWorkerThreadFactory();
    }
    if (parallelism < 0 && // default 1 less than #cores
        (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
        parallelism = 1;
    if (parallelism > MAX_CAP)
        parallelism = MAX_CAP;
    return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE,
                            "ForkJoinPool.commonPool-worker-");
}

parallelism的值有两个影响

系统配置java.util.concurrent.ForkJoinPool.common.parallelism

电脑cup核心数

String pp = System.getProperty
    ("java.util.concurrent.ForkJoinPool.common.parallelism");
if (pp != null)
    parallelism = Integer.parseInt(pp);

parallelism = Runtime.getRuntime().availableProcessors() - 1

asyncPool最终值

一顿分析,parallelism一般等于电脑cup核心数-1

asyncPool = ForkJoinPool.commonPool();

ForkJoinPool.commonPool()

public static ForkJoinPool commonPool() {
    // assert common != null : "static init error";
    return common;
}

common的定义在上面已经展示了

最后返回的是这样一个对象

return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE,
                            "ForkJoinPool.commonPool-worker-");

其中factory就是创建线程的工厂,他的相关定义

String fp = System.getProperty
	("java.util.concurrent.ForkJoinPool.common.threadFactory");
if (fp != null)
    factory = ((ForkJoinWorkerThreadFactory)ClassLoader.
               getSystemClassLoader().loadClass(fp).newInstance());
//-----------------------------------------------------------------------------------
if (factory == null) {
    if (System.getSecurityManager() == null)
        factory = defaultForkJoinWorkerThreadFactory;
    else // use security-managed default
        factory = new InnocuousForkJoinWorkerThreadFactory();
}
//-----------------------------------------------------------------------------------
defaultForkJoinWorkerThreadFactory =
    new DefaultForkJoinWorkerThreadFactory();

默认情况下,factory就是DefaultForkJoinWorkerThreadFactory

DefaultForkJoinWorkerThreadFactory

static final class DefaultForkJoinWorkerThreadFactory
    implements ForkJoinWorkerThreadFactory {
    public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
        return new ForkJoinWorkerThread(pool);
    }
}

ForkJoinWorkerThread(ForkJoinPool pool)

protected ForkJoinWorkerThread(ForkJoinPool pool) {
    // Use a placeholder until a useful name can be set in registerWorker
    super("aForkJoinWorkerThread");
    this.pool = pool;
    this.workQueue = pool.registerWorker(this);
}

registerWorker(this)

final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
    UncaughtExceptionHandler handler;
    wt.setDaemon(true);                           // configure thread
    if ((handler = ueh) != null)
        wt.setUncaughtExceptionHandler(handler);
    WorkQueue w = new WorkQueue(this, wt);
    int i = 0;                                    // assign a pool index
    int mode = config & MODE_MASK;
    int rs = lockRunState();
    try {
        WorkQueue[] ws; int n;                    // skip if no array
        if ((ws = workQueues) != null && (n = ws.length) > 0) {
            int s = indexSeed += SEED_INCREMENT;  // unlikely to collide
            int m = n - 1;
            i = ((s << 1) | 1) & m;               // odd-numbered indices
            if (ws[i] != null) {                  // collision
                int probes = 0;                   // step by approx half n
                int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
                while (ws[i = (i + step) & m] != null) {
                    if (++probes >= n) {
                        workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                        m = n - 1;
                        probes = 0;
                    }
                }
            }
            w.hint = s;                           // use as random seed
            w.config = i | mode;
            w.scanState = i;                      // publication fence
            ws[i] = w;
        }
    } finally {
        unlockRunState(rs, rs & ~RSLOCK);
    }
    wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
    return w;
}

上面DefaultForkJoinWorkerThreadFactorynewThread方法返回的就是一个新的线程

在创建新的线程时,其构造函数中pool.registerWorker(this);该方法传入线程本身

在这个方法中看到wt.setDaemon(true); 设置,就将该线程设置为守护线程了(wt就是线程本身)

自定义线程池

我们可以通过设置系统参数来改变默认线程池的一些配置,但是不推荐。因为这样的修改影响的是整个程序,不好控制。

CompletableFuture的方法中是有参数提供,让我们使用自定义线程池的。

看下来你会发现,xxxAsync()方法都是成双出现的,是有重载的,e :

runAsync(Runnable runnable);

runAsync(Runnable runnable, Executor executor);   

Executor参数就可以传我们自定义的线程池

public static void main(String[] args) {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        System.out.println("do something ......");
    }, executorService);

    future.join();
    executorService.shutdown();
}

使用完线程池,记得关闭,线程池中默认创建的线程不是守护线程,不关闭的话,你的程序不会结束。

自定义线程池推荐使用new ThreadPoolExecutor();而不是直接使用工具提供的四种线程池,他们都存在各自的缺陷,详细信息可以查看线程池相关的资料,要按自己的业务需要,创建适合的线程池。