1.合理利用线程池能够带来三个好处:
- 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 第二:可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多的资源竞争,避免阻塞。
- 第三:提供定时执行、定期执行、单线程、并发数等控制。
2.使用方式之一:
3.参数说明
1)corePoolSize(线程池的基本大小,核心线程数量):
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于核心线程的数量时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
2)maximumPoolSize(线程池最大大小):
线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
3)keepAliveTime(线程活动保持时间):
线程池的工作线程空闲后(指大于核心又小于max的那部分线程),保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,让空闲线程多活一会,提高线程的利用率。
4)TimeUnit(线程活动保持时间的单位):
可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
5)runnableTaskQueue(任务队列):
用于保存等待执行的任务的阻塞队列。
在重复一下新任务进入时线程池的执行策略:
如果运行的线程少于corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存入queue中,而是直接运行)
如果运行的线程大于等于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
主要有3种类型的BlockingQueue:
无界队列
队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列做为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,会导致cpu和内存飙升服务器挂掉。
有界队列
常用的有两类,一类是遵循FIFO(先进先出)原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
同步移交队列
如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
6)ThreadFactory:
用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
7)RejectedExecutionHandler(饱和策略):
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。
AbortPolicy:直接抛出异常。
CallerRunsPolicy:将任务回退给调用者来直接运行。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
自定义:当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
4.代码验证
之前整理了以上关于线程池的内容,由于一直没有用到线程池,对于里面参数的理解没有一个具体的概念。正好有个项目用到了@Async注解,需要自定义线程池,因此就用代码加深了对参数的理解。
异步方法类:
@Component
public class Print {
@Async("asyncThreadPoolExecutor")
public void aysncPrint(){
System.out.println("--- async: thread name: " + Thread.currentThread().getName());
}
}
调用异步方法的类:
@Component
public class AsyncDemo {
@Autowired
private Print print;
public void test() {
int number = 10;
for (int i = 1; i <= number; i++) {
//异步方法
print.aysncPrint();
}
}
}
测试类:
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncTest {
@Autowired
private AsyncDemo asyncDemo;
@Test
public void test(){
asyncDemo.test();
}
}
线程池主要有以下配置:
spring.task.execution.pool.core-threads = 3
spring.task.execution.pool.max-threads = 5
spring.task.execution.pool.queue-capacity = 100
spring.task.execution.pool.keep-alive = 10
@Configuration
public class ThreadsConfig implements AsyncConfigurer {
@Value("${spring.task.execution.pool.core-threads}")
private int corePoolSize;
@Value("${spring.task.execution.pool.max-threads}")
private int maxPoolSize;
@Value("${spring.task.execution.pool.queue-capacity}")
private int queueCapacity;
@Value("${spring.task.execution.pool.keep-alive}")
private int keepAliveSeconds;
@Bean("asyncThreadPoolExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
//设置核心线程数
threadPool.setCorePoolSize(corePoolSize);
//设置最大线程数
threadPool.setMaxPoolSize(maxPoolSize);
//线程池所使用的缓冲队列
threadPool.setQueueCapacity(queueCapacity);
//等待任务在关机时完成--表明等待所有线程执行完
threadPool.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
threadPool.setAwaitTerminationSeconds(keepAliveSeconds);
// 初始化线程
threadPool.initialize();
return threadPool;
}
}
当核心线程为3,最大线程为5,AsyncDemo类test方法number属性为1(执行1次异步方法调用)时,输出如下:
--- async: thread name: asyncThreadPoolExecutor-1
可以看出线程池只安排了一个线程asyncThreadPoolExecutor-1执行任务。
便于展示,根据实验得出以下表格:
结论:当任务到达时,如果当前运行的线程少于corePoolSize,则 Executor始终首选添加新的线程执行任务,而不是把任务放入队列。当创建的线程数大于等于 corePoolSize,则 Executor始终首选将任务加入队列,而不添加新的线程,任务由核心线程执行。当线程池里核心线程和队列都满时(大于实验中核心线程3个+100个任务 ==103)才创建新的线程执行任务。但当创建的线程大于最大线程数+任务数时(106),由于设置的饱和策略是默认的AbortPolicy,直接抛出异常。