1:ThreadPoolExcutor
(1):ThreadPoolExcutor构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
(2):构造函数的参数
- corePoolSize核心线程数目(最多保留的线程数)
- maximumPoolSize最大线程数目
- keepAliveTime生存时间-针对救急线程
- unit时间单位-针对救急线程
- workQueue阻塞队列
- threadFactory线程工厂-可以为线程创建时起个好名字
- handler拒绝策略
(3):图示 核心线程数 最大线程数 阻塞队列
- 核心线程数就是我们银行一直正常开的窗口
- 阻塞队列就是银行常用窗口人数满了,然后就在后候客区等待的顾客
- 救急线程就是阻塞队列满了 不得不再开几个营业窗口
- 最大线程数目 = 救急线程数目 + 核心线程数目
(4):一个任务在线程池的流程
- 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
- 当线程数达到corePoolSize并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue队列排
队,直到有空闲的线程。 - 如果队列选择了有界队列,那么任务超过了队列大小时,会创建maximumPoolSize-corePoolSize数目的
线程来救急。 - 如果线程到达maximumPoolSize仍然有新任务这时会执行拒绝策略。拒绝策略jd提供了4种实现,其它
著名框架也提供了实现- AbortPolicy让调用者抛出RejectedExecutionException异常,这是默认策略
- CallerRunsPolicy让调用者运行任务
- DiscardPolicy放弃本次任务
- DiscardOldestPolicy放弃队列中最早的任务,本任务取而代之
- Dubbo的实现,在抛出RejectedExecutionException异常之前会记录日志,并dump线程栈信息,方便定
位问题 - Netty的实现,是创建一个新线程来执行任务
- ActiveMQ的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
- PinPoint的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
- 当高峰过去后,超过corePoolSize的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由
- keepAliveTime和unit来控制。
2:固定容量的线程池(newFixedThreadPool)
(1):构造函数
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
(2):特点
- 核心线程数==最大线程数
(没有救急线程被创建),因此也无需超时时间 - 阻塞队列是无界的,可以放任意数量的任务
- 评价
适用于任务量已知,相对耗时的任务
(3):代码示例
public class text02 {
public static void main(String[] args) {
ExecutorService executorService1 = Executors.newFixedThreadPool(2);
//固定线程数 核心线程数 == 最大线程数 无救急线程 并且线程不会关闭
//线程池里开启一个线程 并执行一个任务
executorService1.execute(() -> {
System.out.println(Thread.currentThread().getName() + "->1");
});
//线程池再开启一个线程 并执行一个任务
executorService1.execute(() -> {
System.out.println(Thread.currentThread().getName() + "->2");
});
//此时又来了一个任务,因为前面的任务执行时间比较短,所以再来任务的话,不会进入阻塞队列
//而是会用前面已经开启的线程继续执行任务
executorService1.execute(()-> {
System.out.println(Thread.currentThread().getName()+"->3");
});
}
}
3:可缓存的线程池
(1):构造函数
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
(2):特点
- 核心线程数为0,最大线程数为Interger.MAX_VALUE,救急线程的空闲存活时间是60s
- 没有核心线程 全部都是救急线程
- 救急线程可以无线创建
- 队列采用了SynchronousQueue实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手
交货) - 评价
整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线
程。==适合任务数处比较密集,但每个任务执行时间较短的情况 ==(如果任务时间比较长,那么就会不断创建线程,消耗系统性能)
4:单线程池
(1):构造函数
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(2):特点
- 核心线程数为1,最大线程数也为1,这个线程一直存活,不会释放
- 队列是无界队列,当任务数目大于1的时候,任务会被放入无界队列
- 和普通单线程的区别
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建
一个线程,保证池的正常工作 - 和固定容量的线程池只开辟一个线程的区别
Executors…newSingleThreadExecutor()线程个数始终为1,不能修改
FinalizableDelegatedExecutorService应用的是装饰器模式,只对外暴露了ExecutorService接口,因此不
能调用ThreadPoolExecutor中特有的方法
Executors.newFixedThreadPool(1)初始时为l,以后还可以修改
对外暴露的是ThreadPoolExecutor对象,可以强转后调用setCorePoolSize等方法进行修改
5:固定数量线程池…和ThreadPoolExecutor的区别联系
(1)如何创建固定数量的线程池,可缓存的线程池,单线程池
ExecutorService executorService1 = Executors.newFixedThreadPool(5);
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
ExecutorService executorService3 = Executors.newCachedThreadPool();
(2)和ThreadPoolExcutor的联系
我们将对应的线程池点进去,发现其实底层还是靠ThreadPoolExcutors来实现
5:自定义线程池
(1):自定义线程池的规则
我们采用 ThreadPoolExcutors来创建,这使阿里爸爸开发手册中明文推荐的,主要是其他线程池实现类都有OOM(内存用完了)的风险
(2):关于线程池的 Executor工具类的execute()方法
- 当有新的任务要处理时,先看线程池中的线程数量是否大于corePoolSize,再看缓冲队列workQueue是否满,最后看线程池 中的线程数量是否大于maximumPoolSize
另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池 - 也就是excutor()方法中会创建线程执行任务,但是这个线程的数量是有限的,虽然数量有限,但是核心线程可以重复执行任务,救急线程在空闲存活时间的里也可以执行任务
(3):代码示例
public class text03 {
public static void main(String[] args) {
Executors.newCachedThreadPool();
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,//核心线程数
5,//最大线程数 = 核心线程数 + 救急线程数(当阻塞队列)
3,//救济线程的空闲存活时间
TimeUnit.MINUTES,//时间的单位
new LinkedBlockingQueue<>(3),//阻塞队列默认为无界队列但是我将其设置为3
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()//拒绝策略,一旦超过最大线程数就抛异常
);
//我们在一个for循环内
//当我们同时来了9个任务的时候,会有2个任务去核心线程去执行,还有3个任务进入阻塞队列,还有3个任务会去救急线程那去执行
//那么我们的还有一个线程无法执行,此时会采取拒绝策略 ;
//我们阻塞队列中的任务是等着 救急线程 或者 核心线程去执行完手里的任务去执行的
for (int i = 1; i <= 9; i++) {
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+"--->"+"ok");
});
}
}
}
(4):关于拒绝策略
- new ThreadPoolExecutor.AbortPolicy() 这个是默认的拒绝策略 是抛出异常的
- new ThreadPoolExecutor.CallerRunsPolicy() 这个是让调用者去执行这个任务
- new ThreadPoolExecutor.DiscardPolicy() 这个是直接丢掉任务 不抛异常
- new ThreadPoolExecutor.DiscardOldestPolicy() 丢掉最早执行的任务 然后再执行本任务