SpringBoot使用线程池技术实现异步调用功能

在这里插入图片描述

一、知识点

1.什么是线程池?

在这里插入图片描述

2.为什么要使用线程池?

在这里插入图片描述

3.线程池有什么作用?

在这里插入图片描述

4.说说几种常见的线程池及使用场景

在这里插入图片描述

在这里插入图片描述

5.线程池中的几种重要的参数

在这里插入图片描述

6.java线程池的核心线程数与最大的线程数的区别?

在这里插入图片描述

7.说说线程池的拒绝策略

第一种说法:

在这里插入图片描述

第二种说法:

线程池的拒绝策略用于处理当线程池无法接受新任务时的情况。当线程池中的工作线程已经达到最大线程数,并且任务队列也已满时,就会触发拒绝策略来处理新提交的任务。

Java 中的 ThreadPoolExecutor 类提供了四种内置的拒绝策略:

  • AbortPolicy(默认):该策略直接抛出 RejectedExecutionException 异常,阻止任务的提交。
  • CallerRunsPolicy:该策略将任务回退给调用者,即在提交任务的线程中执行该任务。这样可以降低新任务的流量,但是可能会影响调用线程的性能。
  • DiscardOldestPolicy:该策略丢弃任务队列中最旧的任务,然后尝试重新提交新的任务。通过使用这种策略,可以保证正在执行的任务能够被优先处理。
  • DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是一种比较好的选择。

除了上述内置的拒绝策略外,你还可以自定义拒绝策略,只需实现 RejectedExecutionHandler 接口并重写 rejectedExecution() 方法。例如:

import java.util.concurrent.*;

public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自定义处理逻辑
        System.out.println("Task rejected: " + r.toString());
        // 可以根据实际需求进行拒绝策略的具体实现
    }
}

public class ThreadPoolExample {
    public static void main(String[] args) {
        int corePoolSize = 2;
        int maximumPoolSize = 4;
        long keepAliveTime = 60;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
        RejectedExecutionHandler handler = new CustomRejectedExecutionHandler();

        ExecutorService executor = new ThreadPoolExecutor(corePoolSize,
                                                           maximumPoolSize,
                                                           keepAliveTime,
                                                           unit,
                                                           workQueue,
                                                           handler);

        // ...
    }
}

在上述示例中,我们创建了一个自定义的拒绝策略类 CustomRejectedExecutionHandler,并将其传递给线程池的构造函数。在 rejectedExecution() 方法中,你可以根据实际需求来处理被拒绝的任务。

选择合适的拒绝策略取决于应用程序的需求和特定场景。希望这些解释能够帮助到你!如果还有其他问题,请随时提问。

8.线程池的关闭

在这里插入图片描述

9.(了解)线程池都有哪几种工作队列

第一种说法:

在这里插入图片描述

第二种说法:

线程池中的工作队列决定了任务的处理顺序和方式。Java 中的 ThreadPoolExecutor 类提供了三种内置的工作队列,它们分别是:

  • ArrayBlockingQueue:这是一个基于数组实现的有界阻塞队列,可以存储固定数量的任务。当任务提交时,如果线程池中的线程数还没有达到核心线程数,则会创建新的线程来执行任务。如果线程池中的线程数已经达到核心线程数,并且队列已满,则新提交的任务将会被阻塞,直到有空闲线程或者队列空出位置。
  • LinkedBlockingQueue:这是一个基于链表实现的无界阻塞队列,可以存储任意数量的任务。当任务提交时,如果线程池中的线程数还没有达到核心线程数,则会创建新的线程来执行任务。如果线程池中的线程数已经达到核心线程数,并且队列已满,则新提交的任务将会被放在队列末尾等待处理。
  • SynchronousQueue:这是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。也就是说,该队列会立即将任务交给线程执行,如果没有空闲线程,则会直接创建新线程执行任务,因此最大线程数的设置将被忽略。

除了上述内置的工作队列外,还可以自定义工作队列,只需实现 BlockingQueue 接口并重写其中的方法。例如:

import java.util.concurrent.*;

public class CustomBlockingQueue implements BlockingQueue<Runnable> {
    // 自定义实现逻辑,可以使用链表、数组等数据结构
}

public class ThreadPoolExample {
    public static void main(String[] args) {
        int corePoolSize = 2;
        int maximumPoolSize = 4;
        long keepAliveTime = 60;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new CustomBlockingQueue();

        ExecutorService executor = new ThreadPoolExecutor(corePoolSize,
                                                           maximumPoolSize,
                                                           keepAliveTime,
                                                           unit,
                                                           workQueue);

        // ...
    }
}

在上述示例中,我们创建了一个自定义的工作队列类 CustomBlockingQueue,并将其传递给线程池的构造函数。在 CustomBlockingQueue 类中,你可以根据实际需求来实现具体的队列逻辑。

选择合适的工作队列取决于应用程序的需求和特定场景。希望这些解释能够帮助到你!如果还有其他问题,请随时提问。

10.线程池创建

<!--引入commons-lang3,为了创建阿里线程池-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
//这里使用的是ThreadPoolExecutor的完整版构造函数
private static final ThreadPoolExecutor singlePool = new ThreadPoolExecutor(10,10,100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
        new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()
        ,new ThreadPoolExecutor.CallerRunsPolicy());

//下面的代码就是直接执行异步调用的代码
@Test
public void testThread(){
    singlePool.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("index:1");
            System.out.println(Thread.currentThread().getName() + ": run");
        }
    });
}

在这里插入图片描述

二、问题

问题1:ExecutorService和ThreadPoolExecutor联系?

ExecutorService

在这里插入图片描述

ThreadPoolExecutor

在这里插入图片描述

详细链接描述↓

问题2:多线程批处理代码

注意:多线程批处理方案,需要用到阻塞队列+线程池实现,阻塞队列是需要它的延迟效果,而之所以不用线程的sleep或者锁+唤醒方案,是因为太费事且复杂。

要求:假设我有个map里面有100个值,我想实现批处理20个线程每隔5秒延期处理一次。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Test
    void parseString13() {
        Map<Integer, String> map = new HashMap<>();
        for (int i = 0; i < 100; i++) {
            map.put(i, "" + i);
        }

        final DelayQueue<DelayedElement> delayQueue = new DelayQueue<>();
        int i = 0;
        long delay = 5000;
        // 根据指令集合,按照核心线程数分批次执行,每批次间隔时间暂时设置为5s
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            Integer neId = entry.getKey();
            String cmd = entry.getValue();
            if (i != 0 && i % 20 == 0) {
                delay = delay + 5000;
            }
            DelayedElement element = new DelayedElement(delay, cmd, neId);
            delayQueue.offer(element);
            i++;
        }
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        try {
            if (delayQueue.size() > 0) {
                do {
                    DelayedElement element = delayQueue.take();
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println(Thread.currentThread().getName() + ",current time:" + new Date() + ",element:" + element);
                        }
                    });
                } while (delayQueue.size() != 0);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("InterruptedException:" + e.getMessage());
        }
    }
pool-1-thread-1,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='0', now=1701937289272, neId='0'}
pool-1-thread-2,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='1', now=1701937289272, neId='1'}
pool-1-thread-4,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='7', now=1701937289272, neId='7'}
pool-1-thread-3,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='3', now=1701937289272, neId='3'}
pool-1-thread-5,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='8', now=1701937289272, neId='8'}
pool-1-thread-7,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='2', now=1701937289272, neId='2'}
pool-1-thread-8,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='5', now=1701937289272, neId='5'}
pool-1-thread-6,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='4', now=1701937289272, neId='4'}
pool-1-thread-9,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='6', now=1701937289272, neId='6'}
pool-1-thread-10,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='15', now=1701937289279, neId='15'}
pool-1-thread-11,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='16', now=1701937289279, neId='16'}
pool-1-thread-12,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='17', now=1701937289279, neId='17'}
pool-1-thread-13,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='18', now=1701937289279, neId='18'}
pool-1-thread-14,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='9', now=1701937289279, neId='9'}
pool-1-thread-15,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='19', now=1701937289279, neId='19'}
pool-1-thread-16,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='10', now=1701937289279, neId='10'}
pool-1-thread-17,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='11', now=1701937289279, neId='11'}
pool-1-thread-18,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='12', now=1701937289279, neId='12'}
pool-1-thread-20,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='14', now=1701937289279, neId='14'}
pool-1-thread-19,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='13', now=1701937289279, neId='13'}
pool-1-thread-1,current time:Thu Dec 07 16:21:39 CST 2023,element:DelayedElement{delay=10000, expire=1701937299279, msg='31', now=1701937289279, neId='31'}
......

问题3:创建线程有2种方式,我用哪种?

方式1:
ThreadPoolExecutor singlePool = new ThreadPoolExecutor(10,10,100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
new BasicThreadFactory.Builder().namingPattern(“schedule-pool-%d”).daemon(true).build()
,new ThreadPoolExecutor.CallerRunsPolicy());

方式2:ExecutorService executorService = Executors.newFixedThreadPool(20);

结论:
如果简单的创建线程池就用“方式2”,只需指定线程数即可;
如果想对线程池的全面控制和配置请使用“方式1”。

问题4:方法【execute(Runnable)、submit(Runnable)、…】使用说明

注意:有这么多方法,我究竟用哪个?

下面是委托任务给ExecutorService的一些不同的方式:

  • execute(Runnable)
  • submit(Runnable)
  • submit(Callable)
  • invokeAny(…)
  • invokeAll(…)

下面来逐个看看这些方法。

execute(Runnable)

execute(Runnable) 方法接受一个java.lang.Runable对象的实例,并异步执行之。下面是一个使用ExecutorService执行Runnable的例子:

ExecutorService executorService = Executors.newSingleThreadExecutor();
 
executorService.execute(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});
 
executorService.shutdown();

这种方式不能获得Runnable执行的结果,如果有这种需要,你将要使用Callable。

submit(Runnable)

submit(Runnable) 方法也接收一个Runnable接口的具体实现,并返回一个Future对象。Future对象可以用来检测Runable是否执行完成。

Future future = executorService.submit(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});
 
future.get();  //returns null if the task has finished correctly.

submit(Callable)

submit(Callable)方法与submit(Runnable)方法相似,除了接收的参数有所不同。Callable实例非常类似于Runnable,不同的是call方法可以返回一个结果,Runnable.run()方法不能返回一个结果(因为是void类型),就算线程执行完了,成功了future.get()也只是得到null

可以通过submit(Callable)方法返回的Future对象获取Callable的结果。下面是一个使用Callable的例子:

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        System.out.println("Asynchronous Callable");
        return "Callable Result";
    }
});
 
System.out.println("future.get() = " + future.get());

上面代码的输出结果是:

Asynchronous Callable
future.get() = Callable Result

invokeAny(…)

invokeAny()方法接收一个Callable对象或者Callable的子接口实例的集合作为参数,这个方法不会返回Future,但会返回集合中某一个Callable的结果。你不能确定你得到是哪个Callable的结果。只是已执行完成的Callable中的一个。

如果一个任务已经完成(或者抛出了异常),剩余的Callable任务将被取消。
下面是示例代码:

ExecutorService executorService = Executors.newSingleThreadExecutor();
 
Set<Callable<String>> callables = new HashSet<Callable<String>>();
 
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 1";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 2";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 3";
    }
});
 
String result = executorService.invokeAny(callables);
 
System.out.println("result = " + result);
 
executorService.shutdown();

示例代码将会打印给定的Callable集合中一个Callable任务返回的结果。我尝试执行了多次,结果是变化的。有时候是“Task1”,有时候是“Task 2”等。

invokeAll(…)

invokeAll()接收一个Callable对象的集合作为参数,该方法会调用你传给他的集合中的所有Callable对象。Invoke()会返回一个Future对象的列表,通过这个列表你可以获取每一个Callable执行的结果。

一个任务可能会因为一个异常而结束,因此这时任务并不是真正意义上执行成功了。这在Future上是没有办法来判断的。

处理一个任务的容器(collection),并返回一个Future的容器。两个容器具有相同的结构,这里提交的任务容器列表和返回的Future列表存在顺序对应的关系。

下面是示例代码:

ExecutorService executorService = Executors.newSingleThreadExecutor();
 
Set<Callable<String>> callables = new HashSet<Callable<String>>();
 
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 1";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 2";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 3";
    }
});
 
List<Future<String>> futures = executorService.invokeAll(callables);
 
for(Future<String> future : futures){
    System.out.println("future.get = " + future.get());
}
 
executorService.shutdown();
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
可以使用Spring Boot提供的ThreadPoolTaskExecutor来创建线程池。 首先,在你的Spring Boot应用程序中创建一个配置类,如下所示: ```java @Configuration public class ThreadPoolConfig { @Bean public ThreadPoolTaskExecutor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(30); executor.setThreadNamePrefix("ThreadPoolTaskExecutor-"); executor.initialize(); return executor; } } ``` 在上面的代码中,我们创建了一个ThreadPoolTaskExecutor实例,并配置了一些属性: - corePoolSize:线程池的核心线程数。 - maxPoolSize:线程池的最大线程数。 - queueCapacity:线程池的队列容量。 - threadNamePrefix:线程池中线程的名称前缀。 然后,我们可以在Spring Boot应用程序中使用@Autowired来注入ThreadPoolTaskExecutor实例,然后调用submit()方法来提交任务,如下所示: ```java @Service public class MyService { @Autowired private ThreadPoolTaskExecutor executor; public void doSomethingAsync() { executor.submit(() -> { // 执行异步任务 }); } } ``` 在上面的代码中,我们注入了ThreadPoolTaskExecutor实例,并使用submit()方法提交了一个异步任务。 注意,当我们使用线程池处理异步任务时,我们需要非常小心地处理线程池中的异常,否则可能会导致应用程序崩溃。因此,我们建议在异步任务中使用try-catch块来捕获异常,并将异常记录到日志中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘大猫.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值