java多线程在项目中的实际应用-CompletableFuture的使用

实际项目中,多线程都是结合线程池使用的。
多线程实际应用有两种情况,一种是异步任务执行,不需要返回值,另一种是异步任务的查询,需要返回值。下面多线程的写法适用于任何场景,包括是SpringBoot项目中,也是这样写的。看这篇文章之前,可以先看下我写的上篇的多线程的一些知识,链接

1、项目中实际运用—批量执行异步任务

public static void main(String[] args) throws ExecutionException, InterruptedException {
        Integer count = 10;
        // 批量创建时采用多线程
        ExecutorService executorService = new ThreadPoolExecutor(count, count,
                10L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024),
                new ThreadFactoryBuilder()
                        .setNameFormat("excute-pool-%d")
                        .build(),
                new ThreadPoolExecutor.AbortPolicy());
        CountDownLatch countDownLatch = new CountDownLatch(count);

        for (int i = 1; i <= count; i++) {
            Runnable runnable = () -> {
                try {
                    Thread.sleep(5000);
                    //执行任务逻辑代码
                    System.out.println("任务:" + Thread.currentThread().getName());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            };
            executorService.submit(runnable);
        }
        try {
            //任务执行完,才继续往下走,如果不用管任务是否执行完,把该代码注释掉即可
            //具体要结合业务场景使用
            countDownLatch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        System.out.println(".......");
    }

2、批量异步查询-CompletableFuture的使用

CompletableFuture是java8推出的一个非常简便的多线程写法,在上面 “1、项目中实际运用—批量执行异步任务” 中也可以用CompletableFuture来写,而且更简便。

2.1 几种创建方式

1、runAsync()是没返回结果的,supplyAsync()可以指定返回结果。
2、使用没有指定Executor(线程池)的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
3、如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰。

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Runnable runnable = () -> System.out.println("无返回结果异步任务");
        CompletableFuture.runAsync(runnable);

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
                System.out.println("有返回值的异步任务1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello World";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
                System.out.println("有返回值的异步任务2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello World";
        });
        //get方法会阻塞主线程,直到线程池中的线程执行完
        future1.get();
        future2.get();
        //项目中一般会采用这个写法,作用实际是和get方法一样的
        CompletableFuture.allOf(future1,future2).join();
        System.out.println("......");
    }

2.2 线程执行完成后走的方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

1、Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。
2、方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
3、这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Runnable runnable = () -> System.out.println("无返回结果异步任务");
        CompletableFuture.runAsync(runnable);

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
                System.out.println("有返回值的异步任务1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello World";
        });
        //等待任务执行完成
        future1.join();
        // 任务完成或异常方法完成时执行该方法
        // 如果出现了异常,任务结果为null
        future1.whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String t, Throwable action) {
                System.out.println(t + " 执行完成!");
            }
        });
        System.out.println("......");
    }

2.3 线程执行结果转换

将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果。

2.3.1 thenApply

thenApply接收一个函数作为参数,使用该函数处理上一个CompletableFuture调用的结果,并返回一个具有处理结果的Future对象。

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            return "Hello World";
        }).thenApply(str-> str+"-haha");
        System.out.println(future1);

2.3.2 thenCompose

thenCompose的参数为一个返回CompletableFuture实例的函数,该函数的参数是先前计算步骤的结果。

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
CompletableFuture<Integer> future = CompletableFuture
    .supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int number = new Random().nextInt(30);
            System.out.println("第一次运算:" + number);
            return number;
        }
    })
    .thenCompose(new Function<Integer, CompletionStage<Integer>>() {
        @Override
        public CompletionStage<Integer> apply(Integer param) {
            return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                @Override
                public Integer get() {
                    int number = param * 2;
                    System.out.println("第二次运算:" + number);
                    return number;
                }
            });
        }
    });

thenApply 和 thenCompose的区别:
1、thenApply转换的是泛型中的类型,返回的是同一个CompletableFuture;
2、thenCompose将内部的CompletableFuture调用展开来并使用上一个CompletableFutre调用的结果在下一步的CompletableFuture调用中进行运算,是生成一个新的CompletableFuture。

2.4 线程执行结果消费

结果消费是对结果执行Action,Action是一个Consumer函数,根据对结果的处理方式,结果消费函数又可以分为下面三大类:

thenAccept():对单个结果进行消费
thenAcceptBoth():对两个结果进行消费
thenRun():不关心结果,只对结果执行Action

2.4.1 thenAccept

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
CompletableFuture<Void> future = CompletableFuture
    .supplyAsync(() -> {
        int number = new Random().nextInt(10);
        System.out.println("第一次运算:" + number);
        return number;
    }).thenAccept(number ->
                  System.out.println("第二次运算:" + number * 5));

2.4.2 thenAcceptBoth

thenAcceptBoth函数的作用是,当两个CompletionStage都正常完成计算的时候,就会执行提供的action消费两个异步的结果。

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = 1;
                System.out.println("任务1结果:" + number);
                return number;
            }
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = 2;
                System.out.println("任务2结果:" + number);
                return number;
            }
        });

        futrue1.thenAcceptBoth(future2, new BiConsumer<Integer, Integer>() {
            @Override
            public void accept(Integer x, Integer y) {
                System.out.println(x+"----"+y);
                System.out.println("最终结果:" + (x + y));
            }
        });

        System.out.println("......");
    }

2.4.3 thenRun

thenRun也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun会在上一阶段 CompletableFuture计算完成的时候执行一个Runnable,而Runnable并不使用该CompletableFuture计算的结果。且thenRun使用的是主线程执行的。

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            return 1;
        }).thenRun(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).thenRun(()->{
            System.out.println(Thread.currentThread().getName());
        });

        CompletableFuture.allOf(future).join();
        System.out.println("......");
    }

2.5 线程执行结果组合

合并两个线程任务的结果,并进一步处理,且返回处理的结果,与thenAcceptBoth方法不同的是,thenAcceptBoth不返回处理后的结果,这个则返回处理后的结果。

thenCombine

public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor);
 public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int number = 1;
                        return number;
                    }
                });
        CompletableFuture<Integer> future2 = CompletableFuture
                .supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int number = 2;
                        return number;
                    }
                });


        CompletableFuture<Integer> result = future1
                .thenCombine(future2, new BiFunction<Integer, Integer, Integer>() {
                    @Override
                    public Integer apply(Integer x, Integer y) {
                        return x + y;
                    }
                });
        System.out.println("组合后结果:" + result.get());
    }

3、项目中实际运用—多线程批量查询

比如我们需要对大屏上一些数字的统计,这个时候我们就可以用到多线程批量查询了,以下是写法:

 public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        CompletableFuture<List<String>> future1 = CompletableFuture.supplyAsync(
                () -> {
                    List<String> list = Arrays.asList("a");
                   return list;
                }, executorService);

        CompletableFuture<List<String>> future2 = CompletableFuture.supplyAsync(
                () -> {
                    List<String> list = Arrays.asList("b");
                    return list;
                }, executorService);

        CompletableFuture.allOf(future1,future2).join();
        System.out.println(future1.get()+"---"+future2.get());
    }

4、SpringBoot中使用多线程

SpringBoot中使用多线程的写法,和我上面写的是一模一样的,唯一不同的是,可以用SpringBoot默认的自己的线程池,只需要一个注入,就可以获取到默认的线程池了。

@Resource
private ThreadPoolTaskExecutor executor;

实际上咱们也可以自己声明线程池,因为如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰。

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java多线程编程是一个重要的概念,它可以充分利用计算机的多个核心以提高程序的性能。在Java,你可以使用Thread或Runnable来实现基本的线程编程。而CompletableFuture则是Java 8引入的一个强大的工具,它提供了异步编程的能力,可以更方便地处理异步任务。 下面是一个使用CompletableFuture和runAsync方法结合的例子: ```java import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class Main { public static void main(String[] args) { // 创建一个CompletableFuture对象 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { // 这里是你要异步执行的任务 try { // 模拟一个耗时操作 Thread.sleep(3000); System.out.println("任务执行完成"); } catch (InterruptedException e) { e.printStackTrace(); } }); // 在主线程做一些其他的工作 try { Thread.sleep(100); // 主线程休眠一下 } catch (InterruptedException e) { e.printStackTrace(); } // 等待异步任务完成,返回结果(在这个例子为null) try { future.get(); // 注意这会阻塞,除非有并发修改,否则在正常应用场景应避免使用 } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } } ``` 在这个例子,我们创建了一个CompletableFuture对象,并使用`runAsync`方法来启动一个新的线程来执行一个任务。这个任务只是一个简单的模拟耗时操作(这里使用了`Thread.sleep`)。在主线程,我们等待异步任务完成,并获取其结果。注意,这个例子的`future.get()`会阻塞主线程,除非有并发修改,否则在正常应用场景应避免使用。在实际应用,你可能需要使用更复杂的方式来处理异步任务的结果。 CompletableFuture还提供了许多其他的方法,如`thenApply`、`thenAccept`、`thenCompose`等,可以更方便地处理异步任务的结果和异常。你可以根据需要选择合适的方法来处理你的异步任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北漂IT民工_程序员_ZG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值