代码实例理解线程池的配置属性

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,直接抛出异常。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章