使用CompletableFuture.supplyAsync实现异步操作

如何使用CompletableFuture.supplyAsync实现异步操作(复杂型)

实现的代码封装在function中,也有简单的说明,如下:

    public static void useCompletableFuture_complicated() {
        // 这个方法时描述一般地使用CompletableFuture实现异步操作,即复杂的使用CompletableFuture实现异步操作
        
        // 假设我们有一个Person名字List
        List<String> personNameList = new ArrayList<>();
        
        // 为了方便测试,我们要构造大量的数据add到personNameList,用for循环,名字就是1, 2, 3, ...
        
        // 这里添加1000个名字到personNameList
        for (int i = 0; i < 1000; i++) {
            personNameList.add(String.valueOf(i));
        }
        
        // 假设我们要做的业务是personNameList里的每个人都说一句Hello World, 但是我们不关心他们说这句话的顺序,而且我们希望这个业务能够较快速的完成,所以采用异步就是比较合适的
     
        // 先创建两个活动线程的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        // 开始我们的业务处理
        for (String personName : personNameList) {
            CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    // 模拟业务逻辑,say hello world
                    System.out.println(personName + ": Hello World!");
                    return "task finished!";
                }
            }, executor);
        }
        
        // 关闭线程池executor
        // 说明一下executor必须要显示关闭(它的方法里有介绍),不然线程池会一直等待任务,会导致main方法一直运行
        // 还有就是关闭executor,不会导致之前提交的异步任务被打断或者取消。即之前提交的任务依然会执行到底,只是不会再接收新的任务
        executor.shutdown();
        
        /* 那么关闭线程池之后,我们怎么确定我们的任务是否都完成了呢,可以使用executor.isTerminated()命令
        // 可以看看isTerminated这个方法的说明,简单的说就是调用isTerminated()方法之前没有调用shutdown()方法的话,那么isTerminated()方法返回的永远是false。
        // 所以isTerminated()方法返回true的情况就是在调用isTerminated()方法之前要先调用shutdown()方法,且所有的任务都完成了。
        // 其实调用isTerminated()的目的就是我们对异步任务的结果是care, 我们需要等待异步任务的结果以便我们做下一步的动作。
        // 如果我们不关心异步任务的结果的话,完全可以不用调用isTerminated()。
        */ 
        while (!executor.isTerminated()) {
            System.out.println("no terminated");
            try {
                System.out.println("我要休眠一下");
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }

如何使用CompletableFuture.supplyAsync实现异步操作(简洁型)

简洁的代码如下:

        public static void useCompletableFuture_simple() {
        // 这个方法时描述利用1.8新特性,简单使用CompletableFuture实现异步操作
        
        // 先创建两个活动线程的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
                        
        List<String> nameList = new ArrayList<String>();
        
        for (int i = 0; i < 1000; i++) {
            nameList.add(String.valueOf(i));
        }

        // 使用JDK 1.8的特性,stream()和Lambda表达式: (参数) -> {表达式}
        nameList.stream().forEach(name -> CompletableFuture.supplyAsync((Supplier<String>) () -> {
            print((String) name); // 封装了业务逻辑
            return "success";
        }, executor).exceptionally(e -> {
            System.out.println(e);
            return "false";
        }));
        
        executor.shutdown();        
        
        while (!executor.isTerminated()) {
            System.out.println("no terminated");
            try {
                System.out.println("我要休眠一下");
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

两种方法输出结果一致(不要钻牛角尖)

比喻:会输出1000个: x: Hello World!
x的范围是0-999,它们之间是没有顺序,中间会有’no terminated’和’我要休眠一下’这两句话。

分析

使用JDK1.8新特性代码简洁了很多,格局看起来有上来了。

注意点

  1. Supplier< String >(多了空格是因为无空格不显示)的使用我的无法去掉,我看有的文章不需要Supplier,猜测是JDK版本不一致导致。区别如下:
CompletableFuture.supplyAsync((Supplier<String>) () -> {
CompletableFuture.supplyAsync(() -> {
  1. 使用异步其实也是与线程有关系,所以要关注自己的线程是否及时关闭,以免造成内存泄漏。

更新1

日期:2021-8-14
以上两个例子使用的线程池都是默认的,查看源码:

 /**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可知newFixedThreadPool用的是LinkedBlockingQueue,即阻塞的链表队列,再通过源码看看这个队列的默认大小。

/**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

默认大小是Integer.MAX_VALUE,2^31次方,2147483648,21亿多,可以认为是无限大。即这个任务队列是无界的。如果这个时候每个任务的执行时间是非常长,又不断的加任务进去,就会因为线程处理不完,导致内存飙升。

那如何修改呢?这个我目前也在寻找答案。有结果定会更新出来。

更新2(终于来了)

日期:2022-1-14
目的:解决上面使用默认的线程池,池大小是无界的问题。
调整了两个部分,请看下面:
<1>. executor的调整
原来(AS_IS):

        // 先创建两个活动线程的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);

修改(TO_BE):

        //使用阿里巴巴推荐的创建线程池的方式
        //通过ThreadPoolExecutor构造函数自定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                10,
                1L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                new ThreadPoolExecutor.CallerRunsPolicy());

简单说明:通过构造函数自定义线程池参数,参数的意义大家可以自行百度或者查看源码。

<2>. forEach的调整(事实上与这个问题无关)
原来(AS_IS):

        // 使用JDK 1.8的特性,stream()和Lambda表达式: (参数) -> {表达式}
        nameList.stream().forEach(name -> CompletableFuture.supplyAsync((Supplier<String>) () -> {

修改(TO_BE):

 // 使用JDK 1.8的特性,Lambda表达式: (参数) -> {表达式}
        nameList.forEach(name -> CompletableFuture.supplyAsync((Supplier<String>) () -> {

总的代码:

    public static void useCompletableFuture_simple() {
        // 这个方法时描述利用1.8新特性,简单使用CompletableFuture实现异步操作
        
        //使用阿里巴巴推荐的创建线程池的方式
        //通过ThreadPoolExecutor构造函数自定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                10,
                1L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                new ThreadPoolExecutor.CallerRunsPolicy());
                        
        List<String> nameList = new ArrayList<String>();
        
        for (int i = 0; i < 1000; i++) {
            nameList.add(String.valueOf(i));
        }

        // 使用JDK 1.8的特性,Lambda表达式: (参数) -> {表达式}
        nameList.forEach(name -> CompletableFuture.supplyAsync((Supplier<String>) () -> {
            print((String) name); // 封装了业务逻辑
            return "success";
        }, executor).exceptionally(e -> {
            System.out.println(e);
            return "false";
        }));
        
        executor.shutdown();        
        
        while (!executor.isTerminated()) {
            System.out.println("no terminated");
            try {
                System.out.println("我要休眠一下");
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

好了,问题解决了,大家有什么疑问都可以留言一起讨论。
Thanks.

更新3

日期:2022-2-11
目的:自定义线程池和线程的名称。
当我们完成了线程池和线程工厂的定义后,我们的ThreadPoolExecutor就可以使用自己定义的了,如下:

 		// OwlThreadPoolExecutor是使用阿里巴巴推荐的创建线程池的方式,自定义的一个池,使用単例模式的懒汉式
        ThreadPoolExecutor executor = OwlThreadPoolExecutor.getThreadPoolExecutorInstance();

线程池代码:

package threadpool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class OwlThreadPoolExecutor {

    /**
     * default value
     */
    private static int corePoolSite = 5;
    private static int maxPoolSite = 10;
    private static int queueCapacity = 100;
    private static Long keepAliveTime = 1L;
    
    public static volatile ThreadPoolExecutor threadPoolExecutorInstance = null;
    
    private OwlThreadPoolExecutor() {}
    
    public static void initialize(int corePoolSite, int maxPoolSite, int queueCapacity, long keepAliveTime) {
        OwlThreadPoolExecutor.corePoolSite = corePoolSite;
        OwlThreadPoolExecutor.maxPoolSite = maxPoolSite;
        OwlThreadPoolExecutor.queueCapacity = queueCapacity;
        OwlThreadPoolExecutor.keepAliveTime = keepAliveTime;
    }
    
    public static ThreadPoolExecutor getThreadPoolExecutorInstance() {
        if (threadPoolExecutorInstance == null || threadPoolExecutorInstance.isShutdown()) {
            synchronized (OwlThreadPoolExecutor.class) {
                // double check
                if (threadPoolExecutorInstance == null || threadPoolExecutorInstance.isShutdown()) {
                    System.out.println("The thread pool instance is empty, so need to create.");
                    threadPoolExecutorInstance = new ThreadPoolExecutor(
                            corePoolSite,
                            maxPoolSite,
                            keepAliveTime,
                            TimeUnit.SECONDS,
                            new ArrayBlockingQueue<>(queueCapacity),
                            new OwlThreadFactory("ThreadPool"),
                            new ThreadPoolExecutor.CallerRunsPolicy());
                    System.out.println("The thread pool instance info: " + threadPoolExecutorInstance);
                }
            }
        }
        return threadPoolExecutorInstance;
    }
}

线程工厂代码:
自定义线程工厂并给予名字,方便监控JVM的工具追踪问题。

package threadpool;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class OwlThreadFactory implements ThreadFactory {

    private String namePrefix;
    private final AtomicInteger nextId = new AtomicInteger(1);
    
    public OwlThreadFactory(String whatFeatureOfGroup) {
        namePrefix = "From OwlThreadFactory's " + whatFeatureOfGroup + "-Worker-"; // 线程池名字 + 线程名字前缀
    }
    
    @Override
    public Thread newThread(Runnable task) {
        String name = namePrefix + nextId.getAndIncrement(); // 线程id
        Thread thread = new Thread(null, task, name);
        return thread;
    }

}

使用自定义线程池后的异步代码:

public static void useCompletableFuture_simple() {
        // 这个方法时描述利用1.8新特性,简单使用CompletableFuture实现异步操作
        
        // OwlThreadPoolExecutor是使用阿里巴巴推荐的创建线程池的方式,自定义的一个池,使用単例模式的懒汉式
        ThreadPoolExecutor executor = OwlThreadPoolExecutor.getThreadPoolExecutorInstance();
                        
        List<String> nameList = new ArrayList<String>();
        
        for (int i = 0; i < 1000; i++) {
            nameList.add(String.valueOf(i));
        }

        // 使用JDK 1.8的特性,Lambda表达式: (参数) -> {表达式}
        nameList.forEach(name -> CompletableFuture.supplyAsync((Supplier<String>) () -> {
            print(Thread.currentThread().getName() + ", name=" + (String) name); // 封装了业务逻辑
            return "success";
        }, executor).exceptionally(e -> {
            System.out.println(e);
            return "false";
        }));
        
//        executor.shutdown();        
//        
//        while (!executor.isTerminated()) {
//            System.out.println("no terminated");
//            try {
//                System.out.println("我要休眠一下");
//                TimeUnit.MILLISECONDS.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
    }

说明一下后面的代码为何注释?

//        executor.shutdown();        
//        
//        while (!executor.isTerminated()) {
//            System.out.println("no terminated");
//            try {
//                System.out.println("我要休眠一下");
//                TimeUnit.MILLISECONDS.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }

首先这个线程池是公用的,生命周期是和server一致的,所以不能调用shutdown()。
还有就是异步一般是不关心执行结果的,即我交给线程去执行,但是我不关心它什么时候执行完,调用isTerminated(),是在先shutdown之后,看线程是否执行完任务了。这样就相当于同步了,因为要等它执行完成。

main方法不变,运行结果如下:

From OwlThreadFactory's ThreadPool-Worker-1, name=0: Hello World!
From OwlThreadFactory's ThreadPool-Worker-2, name=1: Hello World!
From OwlThreadFactory's ThreadPool-Worker-3, name=2: Hello World!
From OwlThreadFactory's ThreadPool-Worker-4, name=3: Hello World!
From OwlThreadFactory's ThreadPool-Worker-5, name=4: Hello World!
From OwlThreadFactory's ThreadPool-Worker-3, name=8: Hello World!
From OwlThreadFactory's ThreadPool-Worker-5, name=9: Hello World!
From OwlThreadFactory's ThreadPool-Worker-2, name=7: Hello World!
From OwlThreadFactory's ThreadPool-Worker-4, name=6: Hello World!
From OwlThreadFactory's ThreadPool-Worker-1, name=5: Hello World!
From OwlThreadFactory's ThreadPool-Worker-4, name=13: Hello World!
main, name=142: Hello World!

从输出结果我们可以看到线程的名字是我们自定义的。输出了线程名有发现其实主线程(main)也参数了任务的执行,可能是任务量多。
关于ThreadPoolExecutor类的构造器参数后面再出一篇测试看看。

你可能感兴趣的:(异步执行,java)