如何正确使用线程池

具体请参考原创:

《Java线程池实现原理及其在美团业务中的实践》

《Java 线程池及参数动态调节详解》

一、为何要使用线程池
降低资源消耗
       线程的创建和销毁会造成一定的时间和空间上的消耗,线程池可以让我们重复利用已创建的线程。

提高响应速度
       线程池已为我们创建好了线程,当任务到达时可以不需要等到线程创建就能立即执行。

提高线程的可管理性
       线程是稀缺资源,不可能无限的创建,使用线程池可以进行统一分配、调优和监控。

提供更多更强大的功能
        线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

二、线程池核心参数及执行原理
1、核心参数

ThreadPoolExecutor(int corePoolSize, 
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue, 
                   ThreadFactory threadFactory, 
                   RejectedExecutionHandler handler)

参数

说明
corePoolSize核心线程数量,线程池维护线程的最少数量
maximumPoolSize线程池维护线程的最大数量
keepAliveTime非核心线程的最长空闲时间,超过该时间的空闲线程会被销毁
unit

keepAliveTime的单位,有NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒)

workQueue任务缓冲队列(阻塞队列)
threadFactory线程工厂,用于创建线程,一般用默认的即可
handle

线程池对拒绝任务的处理策略

阻塞队列:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。

 1.ArrayBlockingQueue:有界、数组结构、FIFO
 2.LinkedBlockingQueue:有界、单链表结构、FIFO、默认长度Integer.MAX_VALUE
 3.SynchronousQueue:不存储元素、每个put操作必须等待take操作,否则阻塞状态
 4.PriorityBlockingQuene:无界、数组的平衡二叉堆、支持线程优先级排序、默认自然序、同优先级不能保证顺序
 5.DelayQueue:无界、基于PriorityBlockingQuene、以时间作为比较基准的优先级队列,这个时间即延迟时间


ThreadPoolExecutor提供了四种拒绝策略:

1.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认)
2.CallerRunsPolicy:由调用线程处理该任务( 常用)
3.DiscardPolicy:丢弃任务,但是不抛出异常。
4.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
5.ThreadPoolExecutor的运行状态:
 

运行状态状态描述
RUNNING能接受新提交的任务、并且也能处理阻塞队列中的任务
SHUTDOWN关闭状态,不再接受新提交的任务,但可以继续处理阻塞队列中已保存的任务
STOP不能接受新提及的任务,也不处理队列中的任务,会中断正在处理任务的线程
TIDYING所有的任务都已经终止了,workerCount(有效线程数)为0
TERMINATED

在terminated()方法执行完后进入该状态

2、执行原理

三、线程池的创建和使用

1、线程池的创建方式

(1)通过Executors线程工厂类创建(不推荐

1. Executors.newCachedThreadPool();

public static ExecutorService newCachedThreadPool() { 
    return new ThreadPoolExecutor(0, 
                                  Integer.MAX_VALUE, 
                                  60L, 
                                  TimeUnit.SECONDS, 
                                  new SynchronousQueue<Runnable>());
}

该线程池存在的问题:允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。 

2. Executors.newFixedThreadPool(int nThreads);

public static ExecutorService newFixedThreadPool(int nThreads) { 
    return new ThreadPoolExecutor(nThreads, 
                                  nThreads, 
                                  0L, 
                                  TimeUnit.MILLISECONDS, 
                                  new LinkedBlockingQueue<Runnable>());
} 

该线程池存在的问题:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

3. Executors.newSingleThreadExecutor();

public static ExecutorService newSingleThreadExecutor() { 
    return new FinalizableDelegatedExecutorService (
                 new ThreadPoolExecutor(1, 
                                        1,                                   
                                        0L,                                    
                                        TimeUnit.MILLISECONDS, 
                                        new LinkedBlockingQueue<Runnable>())); 
} 

 该线程池存在的问题:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

4. Executors.newScheduledThreadPool(int corePoolSize);

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { 
    return new ScheduledThreadPoolExecutor(corePoolSize); 
} 
 
 
public ScheduledThreadPoolExecutor(int corePoolSize) { 
    super(corePoolSize, 
          Integer.MAX_VALUE, 
          0, 
          NANOSECONDS, 
          new DelayedWorkQueue()); 
}

该线程池存在的问题:允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。 

(2)通过new ThreadPoolExecutor自定义创建(推荐

ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 
                                                 20, 
                                                 60, 
                                                 TimeUnit.SECONDS, 
                                                 new LinkedBlockingQueue<>(200)); 

2、线程池使用规范(阿里巴巴)

创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 自行创建线程,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。
线程池不允许使用 Executors工厂类 去创建,而是通过new ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
3、SpringBoot项目中使用线程池

(1)配置线程池并开启异步任务

@Configuration 
@EnableAsync // 开启异步任务支持 
public class ExecutorConfig { 
    
    // 声明线程池 
    @Bean("taskExecutor") 
    public Executor taskExecutor() { 
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 
        executor.setCorePoolSize(5); 
        executor.setMaxPoolSize(20); 
        executor.setQueueCapacity(2000); 
        executor.setThreadNamePrefix("taskExecutor-"); 
        executor.setRejectedExecutionHandler(new 
                           ThreadPoolExecutor.CallerRunsPolicy()); 
        //执行初始化 
        executor.initialize(); 
        return executor; 
    }
    
}

 (2)在@Async中使用自定义线程池

@Service 
public class TaskService { 
    
    // @Async声明方法为异步方法并自定使用自定义线程池 
    @Async("taskExecutor") 
    public void task1() { 
        // 具体业务 
    } 
    
} 

(3)@Async失效(本质是代理没有生效)

异步方法使用了static修饰
异步方法所在类没有使用@Component或@Service注解进行注释,导致spring无法扫描到异步类
异步方法类应使用@Autowired或@Resource等注解自动注入到使用类中,不能自己手动new对象
没有在启动类或配置类中增加@EnableAsync注解启动异步任务支持
异步方法不能由本类内其他方法调用,必须是外部使用者调用,如果内部方法调用会出现代理绕过的问题,会变成同步操作
(需要继续补充)
四、合理配置线程池参数&线程池参数动态配置&线程池监控
具体参考:

《Java线程池实现原理及其在美团业务中的实践》

《Java 线程池及参数动态调节详解》

1、合理配置线程池参数(并没用通用的计算方式)

业界的一些线程池参数配置方案:

2、线程池参数动态配置&线程池监控 (美团业务中的方案)

线程池参数动态化前后的参数修改流程对比:

 

 线程池参数动态化整体架构:

 线程池参数动态化功能架构:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值