产品经理问我:手动创建线程不香吗,为什么非要用线程池呢?

简介: 每次写线程池的文章时,总会想起自己大三第一次面试就是挂在这上面,当时年少轻狂,连SpringBoot是什么都不知道就敢面阿里,真是初生牛犊不怕虎。

听说微信搜索《Java鱼仔》会变更强哦!


本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看哦


每次写线程池的文章时,总会想起自己大三第一次面试就是挂在这上面,当时年少轻狂,连SpringBoot是什么都不知道就敢面阿里,真是初生牛犊不怕虎。


(一)什么是线程池


线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,我们的程序最终都是由线程进行运作。在Java中,创建和销毁线程的动作是很消耗资源的,因此就出现了所谓“池化资源”技术。


线程池是池化资源技术的一个应用,所谓线程池,顾名思义就是预先按某个规定创建若干个可执行线程放入一个容器中(线程池),需要使用的时候从线程池中去取,用完之后不销毁而是放回去,从而减少了线程创建和销毁的次数,达到节约资源的目的。


(二)为什么要使用线程池


2.1 降低资源消耗


前面已经讲到线程池的出现减少了线程创建和销毁的次数,每个线程都可以被重复利用,可执行多个任务。


2.2 提高系统的响应速度


每当有任务到来时,直接复用线程池中的线程,而不需要等待新线程的创建,这个动作可以带来响应速度的提升


2.3 防止过多的线程搞坏系统


可以根据系统的承受能力,调整线程池中的工作线程的数量,防止因为线程过多服务器变慢或死机。java一个线程默认占用空间为1M,可以想象一旦手动创建线程过多极有可能导致内存溢出。


(三)线程池主要参数


我们可以用Executors类来创建一些常用的线程池,但是像阿里是禁止直接通过Executors类直接创建线程池的,具体的原因稍后再谈。


在了解Executors类所提供的几个线程池前,我们首先来了解一下 ThreadPoolExecutor的主要参数,ThreadPoolExecutor是创建线程池的类,我们选取参数最多的构造方法来看一下:


publicThreadPoolExecutor(intcorePoolSize,
intmaximumPoolSize,
longkeepAliveTime,
TimeUnitunit,
BlockingQueue<Runnable>workQueue,
ThreadFactorythreadFactory,
RejectedExecutionHandlerhandler)


名称 类型

含义

corePoolSize

int

核心线程池的大小
maximumPoolSize
int
最大线程池大小
keepAliveTime

long

线程最大空闲时间

unit

TimeUnit

时间单位

workQueue

BlockingQueue

线程等待队列

threadFactory

ThreadFactory

线程创建工程

handler RejectedExecutionHandler

拒绝策略

3.1 corePoolSize


当向线程池提交一个任务时,如果线程池中已创建的线程数量小于corePoolSIze,即便存在空闲线程,也会创建一个新线程来执行任务,直到创建的线程数大于或等于corePoolSIze。


3.2 maximumPoolSize


线程池所允许的最大线程个数,当队列满了且已经创建的线程数小于maximumPoolSize时,会创建新的线程执行任务。


3.3 keepAliveTime


当线程中的线程数大于corePoolSIze时,如果线程空闲时间大于keepAliveTime,该线程就会被销毁。


3.4 unit


keepAliveTime的时间单位


3.5 workQueue


用于保存等待执行任务的队列


3.6 threadFactory


用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n


3.7 handler


拒绝策略,当线程池和队列满了之后,再加入新线程后会执行此策略。 下面是四种线程池的拒绝策略:


AbortPolicy:中断任务并抛出异常


DiscardPolicy:中段任务但是不抛出异常


DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试提交新任务


CallerRunsPolicy:由调用线程处理该任务


(四)线程池执行流程


当我们了解了ThreadPoolExecutor的七个参数后,我们就可以很快的理解线程池的流程:


网络异常,图片无法展示
|


当提交任务后,首先判断当前线程数是否超过核心线程数,如果没超过则创建新线程执行任务,否则判断工作队列是否已满,如果未满则将任务添加到队列中,否则判断线程数是否超过最大线程数,如果未超过则创建线程执行任务,否则执行拒绝策略。


(五)Executors提供的线程池


executors提供了许多种线程池供用户使用,虽然很多公司禁止使用executors创建线程池,但是对于刚开始解除线程池的人来说,Executors类所提供的线程池能很好的带你进入多线程的世界。


5.1 newSingleThreadExecutor


ExecutorServiceexecutorService=Executors.newSingleThreadExecutor();

听名字就可以知道这是一个单线程的线程池,在这个线程池中只有一个线程在工作,相当于单线程执行所有任务,此线程可以保证所有任务的执行顺序按照提交顺序执行,看构造方法也可以看出,corePoolSize和maximumPoolSize都是1。


publicstaticExecutorServicenewSingleThreadExecutor() {
returnnewFinalizableDelegatedExecutorService        (newThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
newLinkedBlockingQueue<Runnable>()));
}

5.2 newFixedThreadPool


ExecutorServiceexecutorService=Executors.newFixedThreadPool(2);

固定长度的线程池,线程池的长度在创建时通过变量传入。下面是newFixedThreadPool的构造方法,corePoolSize和maximumPoolSize都是传入的参数值


publicstaticExecutorServicenewFixedThreadPool(intnThreads) {
returnnewThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
newLinkedBlockingQueue<Runnable>());
}

5.3 newCachedThreadPool


ExecutorServiceexecutorService=Executors.newCachedThreadPool();

可缓存线程池,这个线程池设定keepAliveTime为60秒,并且对最大线程数量几乎不做控制。


publicstaticExecutorServicenewCachedThreadPool() {
returnnewThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
newSynchronousQueue<Runnable>());
}

观察构造方法,corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制。设定keepAliveTime 为60秒,线程空闲60秒后自动结束,因为该线程池创建无限制,不会有队列等待,所以使用SynchronousQueue同步队列。


5.4 newScheduledThreadPool


创建一个定时的线程池。此线程池支持定时以及周期性执行任务的需求。下面是newScheduledThreadPool的用法:


Threadthread1=newThread(newRunnable() {
@Overridepublicvoidrun() {
System.out.println(Thread.currentThread().getName()+"thread1");
    }
});
Threadthread2=newThread(newRunnable() {
@Overridepublicvoidrun() {
System.out.println(Thread.currentThread().getName()+"thread2");
    }
});
Threadthread3=newThread(newRunnable() {
@Overridepublicvoidrun() {
System.out.println(Thread.currentThread().getName()+"thread3");
    }
});
ScheduledExecutorServicescheduledExecutorService=Executors.newSingleThreadScheduledExecutor();
//在1000ms后执行thread1scheduledExecutorService.schedule(thread1,1000,TimeUnit.MILLISECONDS);
//在1000ms后每隔1000ms执行一次thread2,如果任务执行时间比间隔时间长,则延迟执行scheduledExecutorService.scheduleAtFixedRate(thread2,1000,1000,TimeUnit.MILLISECONDS);
//和第二种方式类似,但下一次任务开始的时间为:上一次任务结束时间(而不是开始时间) + delay时间scheduledExecutorService.scheduleWithFixedDelay(thread3,1000,1000,TimeUnit.MILLISECONDS);

(六)为什么阿里巴巴禁止程序员用

Exectors创建线程池

如果你的idea装了Alibaba Java Codeing Guidelines插件(推荐大家使用,有助于让你的代码更加规范),那么当你写了Exectors创建线程池后会看到提示:


网络异常,图片无法展示
|


并且阿里将这个用法定义为Blocker,即不允许使用,而是让人们用ThreadPoolExecutor的方式创建线程池。原因是通过ThreadPoolExecutor的方式,这样的处理方式让写的人更加明确线程池的运行规则,规避资源耗尽的风险。


网络异常,图片无法展示
|


Executors返回的线程池对象的弊端如下:


1)FixedThreadPool和SingleThreadPool:


  允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。    2)CachedThreadPool:


  允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。    下面是ThreadPoolExecutor创建线程池的简单例子


intcorePoolSize=5;
intmaximumPoolSize=10;
longkeepAliveTime=30;
BlockingQueue<Runnable>blockingQueue=newArrayBlockingQueue(2);
RejectedExecutionHandlerhandler=newThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutorthreadPoolExecutor=newThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, blockingQueue, handler);
threadPoolExecutor.execute(thread1);

(七)在SpringBoot项目中使用线程池


SpringBoot对线程池又做了一层封装,在SpringBoot中,可以通过ThreadPoolTaskExecutor类来创建线程池。需要注意两者的区别ThreadPoolExecutor时JUC包下的类,ThreadPoolTaskExecutor是springframework包下的类。但原理都是一样的。


7.1 项目搭建


首先搭建一个SpringBoot项目,只需要引入web依赖即可


<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

7.2 配置文件配置


线程池的参数尽量写到配置文件里,这样就能根据需要进行修改,在application.properties文件中配置:


myExecutor.corePoolSize=5myExecutor.maxPoolSize=10myExecutor.keepAliveSeconds=30myExecutor.allowCoreThreadTimeOut=falsemyExecutor.queueCapacity=20myExecutor.threadNamePrefix=myExecutor-

7.3 编写一个自己的线程池



新建一个包叫config,新建类ExecutorConfig ,首先参数的值从配置文件中获取,接着用ThreadPoolTaskExecutor 创建一个自己的线程池,注入到Bean容器中。


@Configuration@EnableAsyncpublicclassExecutorConfig {
@Value("${myExecutor.corePoolSize}")
privateintcorePoolSize;
@Value("${myExecutor.maxPoolSize}")
privateintmaxPoolSize;
@Value("${myExecutor.keepAliveSeconds}")
privateintkeepAliveSeconds;
@Value("${myExecutor.allowCoreThreadTimeOut}")
privatebooleanallowCoreThreadTimeOut;
@Value("${myExecutor.queueCapacity}")
privateintqueueCapacity;
@Value("${myExecutor.threadNamePrefix}")
privateStringthreadNamePrefix;
@Bean("myExecutor")
publicExecutormyExecutor(){
ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();
//核心线程数executor.setCorePoolSize(corePoolSize);
//最大线程数executor.setMaxPoolSize(maxPoolSize);
//线程空闲时间executor.setKeepAliveSeconds(keepAliveSeconds);
//是否保留核心线程数executor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);
//队列长度executor.setQueueCapacity(queueCapacity);
//拒绝策略executor.setRejectedExecutionHandler(newThreadPoolExecutor.AbortPolicy());
//设置线程名称前缀executor.setThreadNamePrefix(threadNamePrefix);
executor.initialize();
returnexecutor;
    }
}


这里需要注意的是有一个方法setAllowCoreThreadTimeOut,当传入参数为true时,所有线程超时后都会被销毁,如果为false,只有超过核心线程数并且超时时才会被销毁。


7.4 编写service并使用


首先写一个service接口:


publicinterfaceDoSomeThing {
/*** 通过线程池异步执行耗时的任务*/publicvoiddoSomeThing();
}

再编写实现类:


@ServicepublicclassDoSomeThingImplimplementsDoSomeThing {
@Override@Async("myExecutor")
publicvoiddoSomeThing() {
System.out.println(Thread.currentThread().getName()+"-in");
try {
Thread.sleep(5000);
        } catch (InterruptedExceptione) {
e.printStackTrace();
        }
System.out.println(Thread.currentThread().getName()+"-out");
    }
}

增加注解@Async("myExecutor")后,这段方法就会异步由myExecutor线程池执行。


7.5 编写一个controller


新建一个IndexController,每次请求执行一次doSomeThing()方法。


@RestControllerpublicclassIndexController {
@AutowiredprivateDoSomeThingdoSomeThing;
@RequestMapping(value="/index",method=RequestMethod.GET)
publicStringindex(){
doSomeThing.doSomeThing();
return"success";
    }
}

7.6 测试


访问十次http://localhost:8080/index,由于设置的核心线程数是5,队列容量是30,因此最多只会用到5个线程资源,结果如下:


网络异常,图片无法展示
|


(八)总结


线程池不算什么很难的技术,但是一定要掌握,面试也会经常问,工作中也会用到。要趁别人不注意的时候悄悄拔尖,然后惊艳所有人,我们下期再见。



相关文章
|
1月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
121 2
|
9月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
413 60
【Java并发】【线程池】带你从0-1入门线程池
|
7月前
|
Java
线程池是什么?线程池在实际工作中的应用
总的来说,线程池是一种有效的多线程处理方式,它可以提高系统的性能和稳定性。在实际工作中,我们需要根据任务的特性和系统的硬件能力来合理设置线程池的大小,以达到最佳的效果。
204 18
|
10月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
491 1
|
9月前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
694 64
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
287 38
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
1602 31
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
12月前
|
Java
.如何根据 CPU 核心数设计线程池线程数量
IO 密集型:核心数*2 计算密集型: 核心数+1 为什么加 1?即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
381 4

热门文章

最新文章