如何正确使用线程池

具体请参考原创:

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

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

一、为何要使用线程池

  • 降低资源消耗

       线程的创建和销毁会造成一定的时间和空间上的消耗,线程池可以让我们重复利用已创建的线程。

  • 提高响应速度

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

  • 提高线程的可管理性

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

  • 提供更多更强大的功能

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

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

1、核心参数

ThreadPoolExecutor(int corePoolSize, 
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue 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:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

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());
}

该线程池存在的问题:允许的创建线程数量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());
} 

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

3. Executors.newSingleThreadExecutor();

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

该线程池存在的问题:允许的请求队列长度 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、线程池使用规范(阿里巴巴)

  1. 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
  2. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 自行创建线程,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。
  3. 线程池不允许使用 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失效(本质是代理没有生效)

  1. 异步方法使用了static修饰
  2. 异步方法所在类没有使用@Component或@Service注解进行注释,导致spring无法扫描到异步类
  3. 异步方法类应使用@Autowired或@Resource等注解自动注入到使用类中,不能自己手动new对象
  4. 没有在启动类或配置类中增加@EnableAsync注解启动异步任务支持
  5. 异步方法不能由本类内其他方法调用,必须是外部使用者调用,如果内部方法调用会出现代理绕过的问题,会变成同步操作
  6. 需要继续补充

四、合理配置线程池参数&线程池参数动态配置&线程池监控

具体参考:

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

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

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

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

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

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

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

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

你可能感兴趣的:(Java,java,spring,线程池,多线程,spring,boot)