2021.10.9:线程池
Java虽然内置了多线程支持,启动一个新线程非常方便,但是创建线程需要操作系统资源,频繁创建和销毁大量线程需要消耗大量资源。
如果一组线程可以复用,那么我们就可以把很多小任务让一组线程来执行,而不是一个任务对应一个新线程。这种能够接收大量小任务并进行分发处理的就是线程池。
简单来说,线程池内部维护了若干个线程,没有任务时,这些线程都处于等待状态,如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。
Java标准库提供了ExecutorService接口表示线程池,其典型用法如下:
//创建固定大小线程池 ExecutorService executor = Executors.newFixedThreadPool(3); //提交任务 executor.submit(task1); executor.submit(task2); executor.submit(task3); executor.submit(task4); executor.submit(task5);
由于ExecutorService只是接口,Java标准库提供的几个常用实现类有:
- FixedThreadPool:线程数固定的线程池;
- CachedThreadPool:线程数根据任务动态调整的线程池;
- SingleThreadExecutor:仅单线程执行的线程池。
创建这些线程池的方法都被封装到Executors中。我们以FixedThreadPool为例,看看线程池的执行逻辑:
import java.util.concurrent.*; public class Main{ public static void main(String [] args){ //创建一个固定大小的线程池 ExecutorService es = Executors.newFixedThreadPool(4); for(int i=0;i<6;i++){ es.submit(new Task(""+i)); } //关闭线程池 es.shutdown(); } } class Task implements Runnable{ private final String name; public Task(String name){ this.name = name; } @Override public void run(){ System.out.println("start task " + name); try{ Thread.sleep(1000); }catch(InterruptedException e){ } System.out.println("end task "+name); } }
结果:
start task 3 start task 0 start task 2 start task 1 end task 3 end task 0 start task 4 start task 5 end task 2 end task 1 end task 4 end task 5
我们观察执行结果,一次性放入6个任务,由于线程池只有固定的4个线程。因此,前4个任务会同时执行,等到有空闲线程后,才会执行后面的两个任务。
线程池在程序结束的时候必须关闭(注意是必须)。
使用shutdown()方法关闭线程时,它会等待正在执行的任务先完成,然后关闭。而shutdownNow()则会立刻停止正在执行的任务,awaitTermination()会等待指定的时间再让线程池关闭。
如果我们把线程池更改为CachedThreadPool,由于这个线程池的实现会根据任务数量动态调整线程池的大小,因此6个任务可以一次性全部同时执行。
如果我们想把线程池的大小限制在4~10之间动态调整,可以写成:
int min = 4; int max =10; ExecutorService es = new ThreadPoolExecutor(min,max,60L,TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
ScheduledThreadPool
还有一种任务,需要定期反复执行,例如,每秒刷新证券价格。这种任务本身固定,但是需要反复执行,这时用ScheduledThreadPool,放入其中的任务可以定期反复执行。
创建:
ScheduledExecutorService ses = Executore.newScheduledThreadPool(4);
我们可以提交一次性任务,它会在指定延迟后只执行一次:
//1s后执行一次性任务: ses.schedule(new Task("one-time"),1,TimeUnit.SECONDS);
如果任务以固定的每3s执行(即在3、6、9...这些秒执行),可以这样写:
//2s后开始执行定时任务,每3s执行: ses.scheduleAtFixedRate(new Task("fixed-rate"),2,3,TimeUnit.SECONDS);
如果任务以固定的3s为间隔执行,可以这样写:
//2s后执行定时任务,以3s为间隔执行: ses.scheduleWithFixedDelay(new Task("fixed-delay"),2,3,TimeUnit.SECONDS);
需要注意FixedRate与FixedDelay的区别——FixedRate是指任务总是以固定的时间间隔触发,不管任务执行了多长时间:
而FixedDelay是指,上次执行完毕后,等待固定的时间间隔,再执行下一次任务:
因此,在使用ScheduledThreadPool时,我们要根据需要选择执行一次、FixedRate、FixedDelay。
Java标准库还提供了一个java.util.Timer类,这个类也可以定期执行任务,但是,一个Timer会对应一个Thread,所以,一个Timer只能定期执行一个任务,多个定时任务必须启动多个Timer,而一个ScheduledThreadPool就可以调度多个定时任务。因此我们完全可以用ScheduledThreadPool取代旧的Timer。
小结
JDK提供了ExecutorService实现线程池的功能:
- 内部维护一组线程,可以高效执行大量小任务;
- Executors提供了静态方法创建不同类型的ExecutorService;
- 必须调用shutdown()关闭ExecutorService;
- ScheduledThreadPool可以定期调度多个任务。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2020-10-11 Chrome:查看HTTP请求的header信息