多线程-线程池

线程池的优势

总体来说,线程池有如下的优势:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

4个参数的设计:

corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

线程池创建的四种方式:

newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。
newScheduledThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

拒绝策略(handler)

线程池拒绝策略是指在线程池中所有线程都被占用的情况下,如何处理新的任务请求。常见的线程池拒绝策略有以下四种:

  1. AbortPolicy(默认策略):直接抛出RejectedExecutionException异常,阻止系统正常运行。
  2. CallerRunsPolicy:直接在主线程中运行被拒绝的任务,如果提交任务的速度过快,可能会导致主线程负载过大而影响系统正常运行。
  3. DiscardPolicy:直接丢弃被拒绝的任务,不予任何处理。
  4. DiscardOldestPolicy:丢弃任务队列中最老的任务,然后重新尝试执行任务提交操作。如果线程池的容量已经达到最大值,那么这些被丢弃的任务将不会被执行。

最佳解决方案取决于应用程序的要求和场景。一般而言,建议使用自定义的拒绝策略以满足特定的需求。
以下是一些可能的自定义拒绝策略:

  1. 队列容量有限制,当队列已满时,将任务直接执行,而不是放入队列。这种策略可以确保任务不会被丢弃,但可能会导致主线程负载过大。
  2. 实现一个自适应的拒绝策略,如果线程池中的线程数已经达到最大值,那么就逐渐增加线程池的最大容量,并减少拒绝策略的频率,以便更好地处理任务请求。
  3. 将任务放入一个分布式的任务队列中,这样可以将任务分发到多个节点上执行,从而提高整体的吞吐量。

最终的最佳解决方案应该是根据具体的业务需求和场景来选择合适的拒绝策略,并进行适当的调整和优化。

问题:

一个项目使用多个线程池还是一个线程池?

一般建议是不同的业务使用不同的线程池,配置线程池的时候根据当前业务的情况对当前线程池进行配置,因为不同的业务的并发以及对资源的使用情况都不同,重心优化系统性能瓶颈相关的业务。

上面的代码可能会存在死锁的情况,为什么呢?画个图给大家捋一捋。
试想这样一种极端情况:假如我们线程池的核心线程数为 n,父任务(扣费任务)数量为 n,父任务下面有两个子任务(扣费任务下的子任务),其中一个已经执行完成,另外一个被放在了任务队列中。由于父任务把线程池核心线程资源用完,所以子任务因为无法获取到线程资源无法正常执行,一直被阻塞在队列中。父任务等待子任务执行完成,而子任务等待父任务释放线程池资源,这也就造成了 “死锁”
解决方法也很简单,就是新增加一个用于执行子任务的线程池专门为其服务。

核心线程会不会回收

关于线程池的回收
核心线程通常不会回收,java核心线程池的回收由allowCoreThreadTimeOut参数控制,默认为false,若开启为true,则此时线程池中不论核心线程还是非核心线程,只要其空闲时间达到keepAliveTime都会被回收。但如果这样就违背了线程池的初衷(减少线程创建和开销),所以默认该参数为false。
设置方法

static ThreadPoolExecutor executor=new ThreadPoolExecutor(8,16,0,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10));
static {
    //如果设置为true,当任务执行完后,所有的线程在指定的空闲时间后,poolSize会为0
    //如果不设置,或者设置为false,那么,poolSize会保留为核心线程的数量
    executor.allowCoreThreadTimeOut(true);
}

keepAliveTime是指当线程池中线程数量大于corePollSize时,此时存在非核心线程,keepAliveTime指非核心线程空闲时间达到的阈值会被回收。

corePoolSize:核心线程最大数量,通俗点来讲就是,线程池中常驻线程的最大数量。线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。

执行流程

image.png

示例代码:

thread:
    # 核心线程池数
    corePoolSize:
    # 最大线程池数
    maxPoolSize:
    # 任务队列的容量
    queueCapacity:
    # 非核心线程的存活时间
    keepAlive:
    # 创建线程的等待时间
    awaitTerminationSeconds:
package com.demo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池
 */
@Configuration
public class ThreadPoolTaskExecutorConfig implements AsyncConfigurer {

    // 当前机器核数
    public static final int cpuNum = Runtime.getRuntime().availableProcessors();

    @Value("${thread.corePoolSize}")
    private Integer corePoolSize;

    @Value("${thread.maxPoolSize}")
    private Integer maxPoolSize;

    @Value("${thread.queueCapacity}")
    private Integer queueCapacity;

    @Value("${thread.keepAlive}")
    private Integer keepAlive;

    @Value("${thread.awaitTerminationSeconds}")
    private Integer awaitTerminationSeconds;

    @Override
    @Bean
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程池数
        threadPoolTaskExecutor.setCorePoolSize(optionl(corePoolSize, cpuNum));
        //最大线程池数
        threadPoolTaskExecutor.setMaxPoolSize(optionl(maxPoolSize, cpuNum * 2));
        //任务队列的容量
        threadPoolTaskExecutor.setQueueCapacity(optionl(queueCapacity, 3));
        //非核心线程的存活时间-最大空闲时间
        threadPoolTaskExecutor.setKeepAliveSeconds(optionl(keepAlive, 10));
        threadPoolTaskExecutor.setThreadNamePrefix("test-thread-");
        // 抛异常规则
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolTaskExecutor.initialize();
        // 创建线程的等待时间
        threadPoolTaskExecutor.setAwaitTerminationSeconds(optionl(awaitTerminationSeconds, 10));
        return threadPoolTaskExecutor;
    }

    /**
     * 判空-替换
     *
     * @param key  原值
     * @param key2 替换值
     * @return 结果
     */
    public Integer optionl(Integer key, Integer key2) {
        // 如果key为空则赋值为key2
        return Optional.ofNullable(key).orElse(key2);
    }

}

public static void main(String[] args) {
        ThreadPoolTaskExecutorConfig threadPoolTaskExecutorConfig = new ThreadPoolTaskExecutorConfig();
        // 创建线程池
        Executor executor = threadPoolTaskExecutorConfig.getAsyncExecutor();
        executor.execute(()->{
            System.out.println("内容");
        });
    }

参考:

线程池详解
线程池创建的四种方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

50W程序员都在看

qiugan

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

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

打赏作者

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

抵扣说明:

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

余额充值