线程池的使用:ExecutorService、CompletionService和CompletableFuture的区别

42 篇文章 0 订阅
30 篇文章 0 订阅

1、前言

       当我们需要处理批量任务的时候,经常会使用线程池来提高处理速度,但线程池的使用方式有多种,今天主要来介绍一下ExecutorService、CompletionService和CompletableFuture的区别。

2、结论

       这里先说结论,后面有代码论证
● ExecutorService:适用于需要管理和调度一组线程任务的场景,可以通过 Future 获取任务结果,它是根据任务的提交顺序来获取结果的。
● CompletionService:适用于需要按任务完成顺序处理结果的场景,基于 ExecutorService 实现。
● CompletableFuture:适用于复杂的异步编排和组合任务,提供更丰富的功能和流式 API,适合现代异步编程需求。 CompletableFuture.supplyAsync,然后再同步调用thenApply。

3代码论证

3.1、ExecutorService

       ExecutorService是根据任务提交的顺序来获取任务处理结果的,也就是先提交的任务先会被获取结果,哪怕后提交的任务先被处理完成了。

package com.myf.Test.Test.current;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadPoolTest {

    public static AtomicInteger ThreadIndex = new AtomicInteger(0);
    public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
            1000L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), task -> {
        Thread thread = new Thread(task);
        thread.setName("threadPoolExecutor-" + ThreadIndex);
        ThreadIndex.incrementAndGet();
        return thread;
    });


    public static void main(String[] args) {
        List<Future<String>> futureList = new ArrayList<>();
        for (int i = 10; i > 0; i--) {
            String a = i + "";
            futureList.add(threadPoolExecutor.submit(() -> add(a)));
        }
        System.out.println("开始获取结果:" + LocalDateTime.now());
        for (Future<String> future : futureList) {
            try {
                System.out.println(future.get() + ";" + LocalDateTime.now());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public static String add(String str) {
        try {
            Thread.sleep(Integer.parseInt(str) * 500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return str + str + ";" + Thread.currentThread().getName();
    }

}

       如上我们创建了threadPoolExecutor线程池,核心线程池和最大线程数都是10,也就是全部都是核心线程,在main方法里创建了10个任务来使用线程池,add方法里做了线程休眠操作,第一个任务会休眠5s,第二个任务会休眠4.5s,以此类推,可知,后提交的任务休眠时间越来越短,最先被处理完,同时将处理任务的线程name打印了出来,我们来看下运行结果。

开始获取结果:2024-09-17T16:14:24.969
1010;threadPoolExecutor-0;2024-09-17T16:14:29.938
99;threadPoolExecutor-1;2024-09-17T16:14:29.938
88;threadPoolExecutor-2;2024-09-17T16:14:29.938
77;threadPoolExecutor-3;2024-09-17T16:14:29.938
66;threadPoolExecutor-4;2024-09-17T16:14:29.939
55;threadPoolExecutor-5;2024-09-17T16:14:29.939
44;threadPoolExecutor-6;2024-09-17T16:14:29.939
33;threadPoolExecutor-7;2024-09-17T16:14:29.939
22;threadPoolExecutor-8;2024-09-17T16:14:29.939
11;threadPoolExecutor-9;2024-09-17T16:14:29.939

       可以看到任务全部提交完到获取到第一个任务的处理结果耗时5s左右,就是因为提交的第一个任务休眠了5s导致的。

3.2、CompletionService

       CompletionService是根据任务完成的顺序来获取执行结果。

package com.myf.Test.Test.current;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CompletionServiceTest {

    public static AtomicInteger ThreadIndex = new AtomicInteger(0);
    public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
            1000L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), task -> {
        Thread thread = new Thread(task);
        thread.setName("threadPoolExecutor-" + ThreadIndex);
        ThreadIndex.incrementAndGet();
        return thread;
    });


    public static void main(String[] args) {
        CompletionService executorCompletionService= new ExecutorCompletionService<>(threadPoolExecutor);
        List<Future<String>> futureList = new ArrayList<>();
        for (int i = 10; i > 0; i--) {
            String a = i + "";
            futureList.add(executorCompletionService.submit(() -> add(a)));
        }
        System.out.println("开始获取结果:" + LocalDateTime.now());
        for (Future<String> future : futureList) {
            try {
                System.out.println(executorCompletionService.take().get() + ";" + LocalDateTime.now());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public static String add(String str) {
        try {
            Thread.sleep(Integer.parseInt(str) * 500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return str + str + ";" + Thread.currentThread().getName();
    }
}

       通过executorCompletionService.take().get()可以阻塞式的获取优先执行完的结果

开始获取结果:2024-09-17T16:21:45.204
11;threadPoolExecutor-9;2024-09-17T16:21:45.657
22;threadPoolExecutor-8;2024-09-17T16:21:46.157
33;threadPoolExecutor-7;2024-09-17T16:21:46.656
44;threadPoolExecutor-6;2024-09-17T16:21:47.158
55;threadPoolExecutor-5;2024-09-17T16:21:47.657
66;threadPoolExecutor-4;2024-09-17T16:21:48.157
77;threadPoolExecutor-3;2024-09-17T16:21:48.658
88;threadPoolExecutor-2;2024-09-17T16:21:49.157
99;threadPoolExecutor-1;2024-09-17T16:21:49.657
1010;threadPoolExecutor-0;2024-09-17T16:21:50.157

       和前面一个相比,提交任务的顺序没有发生变化,但是获取任务执行结果的顺序变了,是因为后提交的任务休眠时间最短,所以会最先被执行完,而且可以看到每个任务执行完的时间都相差500ms左右。符合预期。

3.3、CompletableFuture

       如果我们有这样的一个场景,需要对批量任务执行完的结果做一些逻辑处理,那么我们就可以使用CompletableFuture了。

package com.myf.Test.Test.current;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CompletableFutureTest {


    public static AtomicInteger ThreadIndex = new AtomicInteger(0);
    public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
            1000L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), task -> {
        Thread thread = new Thread(task);
        thread.setName("threadPoolExecutor-" + ThreadIndex);
        ThreadIndex.incrementAndGet();
        return thread;
    });


    public static void main(String[] args) {

        List<CompletableFuture<String>> futureList = new ArrayList<>();
        for (int i = 10; i > 0; i--) {
            String a = i + "";
            futureList.add(CompletableFuture.supplyAsync(() -> add(a), threadPoolExecutor)
                    .thenApplyAsync(CompletableFutureTest::add2, threadPoolExecutor));
        }
        System.out.println("开始获取结果:" + LocalDateTime.now());
        for (CompletableFuture<String> future : futureList) {
            try {
                System.out.println(future.get() + ";" + LocalDateTime.now());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public static String add(String str) {
        try {
            Thread.sleep(Integer.parseInt(str) * 500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return str + str + ";" + Thread.currentThread().getName();
    }

    public static String add2(String str) {

        return str + ";"  + Thread.currentThread().getName();
    }
}

       在这里我们增加了一个add2方法,在任务执行完add方法后,会拿add的出参作为add2方法的入参来运行。
开始获取结果:2024-09-17T16:38:31.378
1010;threadPoolExecutor-0;threadPoolExecutor-0;2024-09-17T16:38:36.331
99;threadPoolExecutor-1;threadPoolExecutor-1;2024-09-17T16:38:36.331
88;threadPoolExecutor-2;threadPoolExecutor-2;2024-09-17T16:38:36.331
77;threadPoolExecutor-3;threadPoolExecutor-3;2024-09-17T16:38:36.332
66;threadPoolExecutor-4;threadPoolExecutor-4;2024-09-17T16:38:36.332
55;threadPoolExecutor-5;threadPoolExecutor-5;2024-09-17T16:38:36.332
44;threadPoolExecutor-6;threadPoolExecutor-6;2024-09-17T16:38:36.332
33;threadPoolExecutor-7;threadPoolExecutor-7;2024-09-17T16:38:36.332
22;threadPoolExecutor-8;threadPoolExecutor-8;2024-09-17T16:38:36.332
11;threadPoolExecutor-9;threadPoolExecutor-9;2024-09-17T16:38:36.332
       我们可以看到,在add方法执行完之后紧接着用add的出参作为入参来来执行add2方法了。但这里获取任务执行结果是根据任务提交的顺序来排序的,这一点可能会影响效率。
       当然我们也可以改成按照任务完成的顺序来获取结果

package com.myf.Test.Test.current;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CompletableFutureTest {


    public static AtomicInteger ThreadIndex = new AtomicInteger(0);
    public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
            1000L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), task -> {
        Thread thread = new Thread(task);
        thread.setName("threadPoolExecutor-" + ThreadIndex);
        ThreadIndex.incrementAndGet();
        return thread;
    });


    public static void main(String[] args) {

        List<CompletableFuture<String>> futureList = new ArrayList<>();
        for (int i = 10; i > 0; i--) {
            String a = i + "";
            futureList.add(CompletableFuture.supplyAsync(() -> add(a), threadPoolExecutor)
                    .thenApplyAsync(CompletableFutureTest::add2, threadPoolExecutor));
        }
        System.out.println("开始获取结果:" + LocalDateTime.now());

        List<CompletableFuture<Void>> orderedFutures = new ArrayList<>();
        for (CompletableFuture<String> future : futureList) {
            CompletableFuture<Void> orderedFuture = future.thenAccept(result -> {
                System.out.println(result + ";" + LocalDateTime.now());
            });
            orderedFutures.add(orderedFuture);
        }

        // 等待所有任务完成
        CompletableFuture.allOf(orderedFutures.toArray(new CompletableFuture[0]));

    }


    public static String add(String str) {
        try {
            Thread.sleep(Integer.parseInt(str) * 500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return str + str + ";" + Thread.currentThread().getName();
    }

    public static String add2(String str) {

        return str + ";" + Thread.currentThread().getName();
    }
}

借助allof来执行,而不是直接通过feature.get来获取执行结果。

开始获取结果:2024-09-17T17:32:18.482
11;threadPoolExecutor-9;threadPoolExecutor-9;2024-09-17T17:32:18.941
22;threadPoolExecutor-8;threadPoolExecutor-8;2024-09-17T17:32:19.442
33;threadPoolExecutor-7;threadPoolExecutor-7;2024-09-17T17:32:19.941
44;threadPoolExecutor-6;threadPoolExecutor-6;2024-09-17T17:32:20.445
55;threadPoolExecutor-5;threadPoolExecutor-5;2024-09-17T17:32:20.944
66;threadPoolExecutor-4;threadPoolExecutor-4;2024-09-17T17:32:21.444
77;threadPoolExecutor-3;threadPoolExecutor-3;2024-09-17T17:32:21.943
88;threadPoolExecutor-2;threadPoolExecutor-2;2024-09-17T17:32:22.445
99;threadPoolExecutor-1;threadPoolExecutor-1;2024-09-17T17:32:22.944
1010;threadPoolExecutor-0;threadPoolExecutor-0;2024-09-17T17:32:23.446
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值