线程池学习

一、概念

线程池:一个容纳多个线程的容器,其中的线程可以反复使用,省去频繁创建线程对象的操作,无需反复创建线程消耗过多资源。

二、线程池的好处

1.降低资源消耗:减少创建销毁线程次数

2.提高相应速度:任务到达后无需创建线程可以立即执行

3.提高线程可管理性:根据系统承受压力,调整线程池中工作线程的数量,防止消耗内存过多,让服务器宕机。(每个线程大概消耗1M内存)

三、线程池的配置

1.实际使用创建线程池的方式:单一、可变、定长都不可用,用自定义线程池。

原因:FixedThreadPool和SingleThreadExecutor底层都是用LinkedBlockingQueue实现的,队列最大长度是Integer.MAX_VALUE,显然会导致OOM。实际使用ThreadPoolExecutor的7个参数,自定义线程池。

2.线程池创建的七个参数

corePoolSize

线程池常驻核心线程数

maximumPoolSize

能够容纳的最大线程数

keepAliveTime

空闲线程存活时间

unit

存活时间单位

workQueue

存放提交但未执行任务的队列

threadFactory

创建线程的工厂类

handler

等待队列满后的拒绝策略

3.工作原理:

新任务到达→如果正在运行的线程数小于corePoolSize,创建核心线程;大于等于corePoolSize,放入等待队列。

如果等待队列已满,但正在运行的线程数小于maximumPoolSize,创建非核心线程;大于等于maximumPoolSize,启动拒绝策略。当一个线程无事可做一段时间keepAliveTime后,如果正在运行的线程数大于corePoolSize,则关闭非核心线程。

4.线程池的拒绝策略

当等待队列满时,且达到最大线程数,再有新任务到来,就需要启动拒绝策略。JDK提供了四种拒绝策略

,分别是。

  1. AbortPolicy:默认的策略,直接抛出RejectedExecutionException异常,阻止系统正常运行。
  2. CallerRunsPolicy:既不会抛出异常,也不会终止任务,而是将任务返回给调用者。
  3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交任务。
  4. DiscardPolicy:直接丢弃任务,不做任何处理。

拒绝策略默认是超出后抛出RejectedExecutionException异常并直接丢弃。

5.corePoolSize和maxiPoolSize 的该如何设置呢?

在设置这两个值之前,首先需要通过任务类型对线程池进行分类, 可以分为IO密集型任务,CPU 密集型任务和混合型任务。

(1)IO密集型任务:主要执行IO操作,由于IO操作时间较长,导致CPU利用率不高,CPU常处于空闲状态。因此线程数通常为CPU核心数2倍。

IO密集型任务要点:允许核心线程数销毁(allowCoreThreadTimeOut(true))、使用有界队列(根据具体需求增加)、优先创建线程而不是加入阻塞队列

(2)CPU密集型任务:主要执行计算任务,CPU利用率高。虽然CPU密集型任务可以并行执行,但并行任务越多,花在任务切换上的时间越多,CPU效率越低。因此CPU密集型任务的线程数等于CPU核心数

(3)混合型任务

任务既要执行计算又要执行IO。相对来说IO操作的执行时间较长,CPU利用率也不是很高。例如WEB服务器的http请求,一次请求包括DB操作、缓存操作等多种操作。

混合型任务有一个计算公式:最佳线程数=(线程等待时间/线程cpu时间+1)*cpu核数

从公式得出: 等待时间所占的比例越高,需要的线程数就越多;cpu所占的比例越高,需要的线程就越少。

公式只是一个理论值,具体还需要结合硬件环境和网络环境不断尝试,获取一个最优值。

四、线程池的使用

1.执行任务

execute和submit的区别

execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务

submit既能提交Runnable类型任务也能提交Callable类型任务。

execute()没有返回值

submit有返回值,所以需要返回值的时候必须使用submit

如果想知道什么时候执行完可搭配CountDownLatch,主线程countDownLatch.await()等待即可。也可以使用待返回参数的Fulture.get()处理

2.开启异步线程

@Autowired ThreadPoolTaskExecutor commonTaskExecutor; //会去匹配 @Bean("commonTaskExecutor") 这个线程池 如果是使用的@Async注解,只需要在注解里面指定bean的名称就可以切换到对应的线程池去了。如下所示: @Async("commonTaskExecutor") public void hello(String name){ logger.info("异步线程启动 started."+name); }

总结@Async可能失效的原因

1.@SpringBootApplication启动类当中没有添加@EnableAsync注解。

2.异步方法使用注解@Async的返回值只能为void或者Future。

3.没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器管理。

解决方法:

1、注解的方法必须是public方法。

2、注解的方法不要定义为static

3、方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。

4、如果需要从类的内部调用,需要先获取其代理类。

全部评论

相关推荐

lllll1234:xd,从你接受offer到offer发送到邮件隔了多久呢
点赞 评论 收藏
分享
2024-11-28 15:01
已编辑
三亚学院 前端工程师
在拧螺丝的西红柿很热情:学校放最前,一旦让看的人找了,找到了还不是比较好的学校,你就寄了,坦诚点放前面
点赞 评论 收藏
分享
评论
点赞
11
分享
牛客网
牛客企业服务