赞
踩
在日常开发中我们经常会使用多线程,但是当线程数量达到一定程度时,频繁的创建线程、执行任务、销毁线程就会造成资源的浪费和性能的开销,那么这个时候就要考虑使用线程池了,它可以很好的避免这个问题,尤其是程序中需要创建大量的生存周期很短的线程时,更应该考虑使用线程池。
线程池(ThreadPool),简单的说,它就是一个装有线程的池子,我们只需把任务(多线程执行的内容)交给线程池来处理,和数据库连接池、Http连接池的概念差不多的,通过维护一定数量的线程来达到多个线程复用的效果,同时它也能够帮我们自动管理线程的生命周期。
线程池的种类有4种,下面会讲,但是他们的底层使用的都是ThreadPoolExcutor这个类,所以只需把这个类搞懂,就基本能解决一些常规的业务了。
五个参数的
/**
* @param corePoolSize 线程池的核心数量
* @param maximumPoolSize 线程池的总数量
* @param keepAliveTime 在任务量大于队列长度需要需要创建的线程数量时,会创建新的临时线程,假如新创建的临时线程把队列中的任务都执行完了,那么接下来这 个临时线程并不是立即释放,而是会等待新的任务与核心线程一起去执行,
* @param unit 设置等待时间的时间单位 DAYS、HOURS、MINUTES等等
* @param workQueue 阻塞队列,用于存放没有执行的任务,只存放由execute()方法提交的、实现Runnable接口的任务。
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue);
六个参数的
/**
* @param corePoolSize 线程池的核心数量
* @param maximumPoolSize 线程池的总数量
* @param keepAliveTime 在任务量大于队列长度需要需要创建的线程数量时,会创建新的临时线程,假如新创建的临时线程把队列中的任务都执行完了,那么接下来这 个临时线程并不是立即释放,而是会等待新的任务与核心线程一起去执行,
* @param unit 设置等待时间的时间单位 DAYS、HOURS、MINUTES等等
* @param workQueue 阻塞队列,用于存放没有执行的任务,只存放由execute()方法提交的、实现Runnable接口的任务。
* @param handler 任务拒绝策略,当任务量大于我们线程池设置的最大线程数时,那么就会触发这个拒绝策略,java中定义的拒绝策略有4中,之后给大家演示。
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler);
ThreadPoolExcutor类总共有4个,熟悉以上两个常用的构造方法即可。
这里我就举一个简单的使用场景,用多线程去网页上下载图片。
创建一个springboot项目
@SpringBootApplication public class SpringbootTemplateApplication { public static void main(String[] args) { ConfigurableApplicationContext application = SpringApplication.run(SpringbootTemplateApplication.class, args); DownloadPicService service = application.getBean(DownloadPicService.class); service.start(); } /** * 往ioc容器中注入一个自定义的线程池 * 核心线程数为30 * 最大线程数为30 * 临时线程等待任务时间为30s * 阻塞队列的长度为10 * 拒绝策略为丢弃新任务 * @return */ @Bean public ThreadPoolExecutor threadPoolExecutor(){ return new ThreadPoolExecutor(30, 30, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.AbortPolicy() ); } }
@Service public class DownloadPicService { private static final Logger logger = LoggerFactory.getLogger(DownloadPicService.class); //将线程池注入 @Autowired public ThreadPoolExecutor threadPoolExecutor; public void start(){ for (int i = 0; i < 41; i++) { //将任务提交到线程池, threadPoolExecutor.execute(new DownloadTask("https://image.baidu.com/xxxxxxxxx")); } } /** * 这里我为了方便,直接创建的是一个静态内部类,实现 Runnable接口 */ public static class DownloadTask implements Runnable{ private String path; //图片地址 public DownloadTask(String path) { this.path = path; } @Override public void run() { FileOutputStream fos = null; try { byte[] bytes = IOUtils.toByteArray(new URI(path)); String uuid = UUID.randomUUID().toString(); String picName = uuid+".jpg"; fos = new FileOutputStream(new File("F:\\labelTest"+File.separator+picName)); IOUtils.write(bytes, fos); logger.info("图片下载成功,{}", picName); }catch (Exception e){ logger.error("下载图片失败,原因:{}", e.getMessage()); }finally { try { if(fos != null){ fos.close(); } }catch (Exception e){ e.printStackTrace(); } } } } }
启动后发现报错
Caused by: java.util.concurrent.RejectedExecutionException: Task com.pihao.service.DownloadPicService$DownloadTask@22f314c7 rejected from java.util.concurrent.ThreadPoolExecutor@4ec3005d[Running, pool size = 30, active threads = 30, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.pihao.service.DownloadPicService.start(DownloadPicService.java:35)
at com.pihao.SpringbootTemplateApplication.main(SpringbootTemplateApplication.java:20)
... 5 more
# 这是什么原因呢?AbortPolicy,这是我设置的线程池拒绝策略,当现有任务数量超过线程池设置的最大线程数时会触发,究其原因,是因为我执行了41个下载任务,而最大线程数设置的40,所以第41个任务就被舍弃了,拒绝执行,抛出错误,前面40个任务继续执行不受影响。
先来看一张原理图
ok,执行步骤就是这样
固定的线程池
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
特点:核心线程数和最大线程数相等,而空闲的存活时间为0毫秒,说明这个参数也没有啥意义,工作队列为最大的Integer.MAX_VALUE大小的阻塞队列。当执行任务时,如果线程都很忙,就会丢到工作队列等有空闲线程时再执行,队列满就执行默认的拒绝策略
单例的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点:核心线程数和最大线程数均为1,空闲的存活时间为0毫秒,工作队列也是最大的Integer.MAX_VALUE大小的阻塞队列。意味着每次执行一个线程,多余的任务先存储到工作队列,一个一个的执行,这样就保证了线程的顺序执行
带缓冲的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:核心线程数为0,最大线程数为最大值Integer.MAX_VALUE,创建的新的临时线程等待60秒后释放,SynchronousQueue这是一个直接提交的队列,意味着每个新任务都会有线程来执行,如果线程池有可用的线程则执行任务,否则就创建一个来执行,线程池中的线程数不确定,一般建议执行速度较快较小的线程,不然这个最大线程池边界过大容易造成内存溢出。
调度线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
特点:延时队列,支持时间控制,详细请查看它的 schedule ( ) 方法
之前说过,当提交的任务总数大于最大线程数时才会触发任务拒绝策略,java中的定义的策略有4中,分别如下
这个是最简单的,直接拒绝后面提交的任务,并抛出拒绝异常
后面的任务不做任务处理,不执行,也不抛出异常
丢弃最老的任务,就是从队列中取出最老的任务,然后放入新进来的任务执行
如果线程池未关闭,则调用主线程来帮忙执行新的任务,这也会导致主线程效率
线程以及线程池这一块面试必问,躲不掉的!!!重点是线程池的执行步骤那一部分。与其每次侥幸心理背面试题还不如静下心来好好把它理解透彻,事半功倍。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。