type
status
date
slug
summary
tags
category
icon
password
ext
order
comment
前言
首先提出两个问题,1.什么是线程池?2.为什么要使用线程池?
什么是线程池
顾名思义,一个池子,里面放的都是线程。
规范点说,线程池是一个存储线程的容器,将线程先创建好后,放入池中交由线程池去管理维护。
为什么使用线程池
借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
比喻一下:不用线程池的时候,程序里运行的那些线程,就像海里的那些鱼儿,不好管理,难以维护控制,说不定还给跑没影了;用线程池的话,就像把那些鱼儿给放入了一个大池塘里,圈养起来,方便管理。
其实管理只是一方面,上面也提到了,还有资源消耗以及响应速度,一个线程可以复用,如果不复用,不断地创建线程->销毁线程也是很消耗资源的,而且创建也需要时间。
而且现在的程序世界里,池化技术无处不在
数据库有数据库连接池,比如常用的阿里的Druid、 DBCP(依赖于commons-pool )、 C3P0
http有http连接池,比如PoolingHttpClientConnectionManager
这两种目的都比较像,复用连接,管理连接,像http池,复用的话就省去TCP3次握手4次挥手的时间,而且连接也不用人为的释放创建,其实都一个意思,就是交由池子去管理维护.
ThreadPoolExecutor类分析
首先看一下ThreadPoolExecutor类结构
ThreadPoolExecutor类结构
Executor接口中定义了一个方法void execute(Runnable command),用于执行线程
ExecutorService接口中定义了shutdown、shutdownNow、isShutdown、isTerminated、awaitTermination、submit、invokeAll、invokeAny,这些方法,用于管理线程以及查看状态
AbstractExecutorService抽象类只实现了invoke,submit,newTaskFor方法,抽象类可以不实现接口的方法
ThreadPoolExecutor类则是我们要使用的线程池,就是具体的实现了
下面挑一个ThreadPoolExecutor类中参数最多的构造函数说一下
ThreadPoolExecutor参数分析
最重要的三个参数
- corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
- maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
- workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
其他几个参数
- keepAliveTime: 当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
- unit: keepAliveTime 参数的时间单位。
- threadFactory: executor 创建新线程的时候会用到。
- handler: 饱和策略,关于饱和策略下面单独介绍一下,四种(AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy)
ThreadPoolExecutor饱和策略
如果当前同时运行的线程数量达到最大线程数量,并且队列也已经被放满了任务时,ThreadPoolExecutor定义了四种策略,都是 ThreadPoolExecutor中的静态内部类
- ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException来拒绝新任务的处理。
- ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
- ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
ThreadPoolExecutor的使用
下面放一个简单练习的代码,注意其中的各项参数值,还有饱和策略采用的是CallerRunsPolicy
是不是发现输出结果中有一个main Runnable[8],这是因为采用的饱和策略 CallerRunsPolicy , 它会调用执行自己的线程运行任务。
我们再将饱和策略改为AbortPolicy,队列满时会抛出异常,如下
ThreadPoolExecutor的execute方法
为了更好理解线程池,我们看一下他的execute方法,首先放张图便于理解
利用Executors创建线程池
一个可根据实际情况调整线程数量的线程池,线程池的线程数量不确定,但若有空闲线程可以复用,
则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。
所有线程在当前任务执行完毕后,将返回线程池进行复用。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
一个固定线程数量的线程池,该线程池中的线程数量始终不变。当有一个新的任务提交时,
线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,
待有线程空闲时,便处理在任务队列中的任务。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
一个固定数量的线程池,支持定时及周期性任务执行,scheduledThreadPool.scheduleWithFixedDelay()方法设置周期
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
只有一个线程的线程池,若多余一个任务被提交到该线程池,
任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
- 作者:Loneking
- 链接:https://loneking.cn/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/91
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。