于:Flash,最近看到线程池,被里面乱七八糟的参数搞糊涂了你能告诉我一些关于它的情况吗
闪电侠:没问题我很擅长这个先说最简单的情况假设有一段您想要异步执行的代码要不要写这样的代码
new thread . start,
于:嗯,最简单的写法好像是这样的。
Flash:当然这种写法可以完成功能,但是你这样写,老王这样写,老张这样写,程序里到处都有这样创建线程的方法能不能写一个统一的工具类给大家调用
于:对,感觉有统一的工具类,比较优雅。
Flash:如果你要设计这个工具类,你会怎么写我先给你设置一个接口,你来实现
public interfaceexecutorpublicvoidexecute,
Yu: emmm,我可能先定义几个成员变量,比如核心线程数,最大线程数...无论如何,那些是混乱的参数。
闪电侠:停!小宇,你现在已经被面试手册深深毒害了首先,忘记所有这些概念,说你被要求写一个最简单的工具类第一反应你会怎么做
头版
于:那我可能就这样了。
//New thread:直接新建一个线程运行classflasheexecuteenimplementsexecutorpublicfoidexecutenetthread。start,
闪电侠:嗯嗯很好你的想法很好
于:啊,我是不是太低了我以为你会骂我
为什么,Doug Lea在JDK源代码注释中给出了这样一个例子,这是最基本的函数在此基础上,你尝试优化一下
第二版
于:还能怎么优化你不是已经用工具类实现了异步执行吗
让我问你一个问题如果有10,000人调用这个工具类提交任务,就会创建10,000个线程来执行它们这肯定是不合适的!你能控制线程数量吗
于:不难我可以将这个任务R放入任务队列,然后只启动一个线程让我们称之为工作线程,从任务队列中获取任务并执行它们这样,无论调用者调用多少次,总会只有一个工作线程在运行,就像这样
Flash:太好了,这个设计有三个重大意义:
1.线程的数量受到控制。
2.队列不仅充当缓冲区,还将任务的提交和执行解耦。
3.最重要的一点是解决每次重复创建和销毁线程带来的系统开销。
于:哇,真的,这么小的变化却有这么大的意义。
修卡:当然,但是只有一个后台工作线程的工人短缺吗如果这个任务队列满了呢
第三版
Yu:哦,确实有些场景只有一个线程很费力,那我就增加工作线程的数量。
Flash:是的,工作线程的数量应该增加,但具体数量应该由用户决定调用时,它被传入姑且称之为核心线程号corePoolSize
于:好吧,我就这样设计。
1.初始化线程池时,直接启动corePoolSize Worker先运行。
2.这些工作线程在无限循环中从队列中取出任务,然后执行它们。
3.execute方法还是直接把任务放在队列里,但是队列满了然后直接丢弃。
闪电侠:太完美了我会奖励你一块费里罗
于:哈哈谢谢,那我再吃一会儿。
修卡:好吧,你可以边吃我边说现在我们实现了一个线程池,它至少没有那么难看,但是仍然有一些小缺陷例如,在初始化期间,创建了一组工作线程在那里运行如果此时没有异步任务被提交执行,那就有点浪费了
于:哦,看来是这样!
于:哦,我不敢相信我温柔的小姨子居然写出这么粗鲁的代码。
修卡:嗯,请先吞下费列罗。
第四版
于:我吃完了,现在脑子不够用我得先消化食物要不你帮我分析一下
Flash:好,现在我们做以下改进。
1.按需创建工作线程:在线程池刚刚初始化的时候,不再立即创建corepowersize工作线程,而是在等待调用者提交任务的同时逐渐创建工作线程,然后在数量达到corepowersize时停止,直接将任务扔入队列然后必须使用一个属性来记录已经创建的工作线程的数量,这个属性称为workCount
2.添加拒绝策略:在实现中是指添加一个参数,参数的类型是一个接口RejectedExecutionHandler,调用方决定实现该类,以便在任务提交失败后执行rejectedExecution方法。
3.增加一个ThreadFactory:在实现上,就是增加一个参数,参数的类型是一个接口ThreadFactory添加工作线程时,不再直接添加newThread,而是调用调用者传入的ThreadFactory实现类的new thread方法
就像下面这样。
于:哇,你还是不错的这个版本应该很完美吧
修卡:不,不,不,它远非完美接下来的改进就看你自己了这里可以给你一个提示
弹性思维
第五版
于:思维灵活哈哈,飞侠,你的术语越来越烂了
闪电侠:咳咳。
于:哦,我的意思是,你一定是说我的代码不灵活吧但是弹性是什么意思呢
Flashmaker:简单来说,在这个场景下,弹性是指在任务提交频繁和任务提交非常不频繁的情况下,你的代码是否有问题。
于:嗯,让我想想当我的线程池中提交的任务数量突然增加时,工作线程和队列全部被占用,我们只能求助于拒绝策略,实际上就是丢弃
闪电侠:是的。
于:这个确实太难了,诶,但是我想了一下,调用者可以通过设置大量的核心线程corePoolSize来解决这个问题。
Flash:是的,但是一般来说,QPS的高峰期很短,为这短暂的高峰期设置大量的核心线程是一种资源浪费看上面这张图不觉得头晕吗
于:是啊,我该怎么办太大或太小了
Flash:我们可以发明一个名为Maximum Threads maximumPoolSize的新属性当核心线程数和队列已满时,新提交的任务仍然可以创建一个新的工作线程,直到工作线程数达到maximumPoolSize,这样可以缓解临时高峰期,用户不必设置过多的核心线程数
于:哦,我觉得有点,但是怎么操作呢。
闪电侠:想象力不好小宇,那你看下面的演示
1.开始时与上一版相同,当workCount lt当corePoolSize时,创建一个新的Worker来执行任务。
2.当工作计数gt,= corePoolSize停止创建新线程,并将任务直接扔入队列。
3.但当队列已满且仍在工作时,计数lt,当maximumPoolSize时,不直接采取拒绝策略,而是创建一个非核心线程,直到workCount = maximumPoolSize,再采取拒绝策略。
于:哦,我怎么没想到呢这样,corePoolSize负责大多数情况下所需的工作线程数,而maximumPoolSize负责在高峰时段临时扩展工作线程数
Flash:没错,高峰期的灵活度做到了,自然就要考虑低谷期了当长时间没有任务提交时,核心线程和非核心线程都处于空闲状态,浪费资源我们可以为非核心线程设置一个超时keepAliveTime当我们长时间无法从队列中获取任务时,我们将停止等待并销毁线程
于:好吧,这次我们的线程池可以在高峰的时候临时扩容,在低谷的时候可以及时回收线程,不浪费资源真的看起来很Q弹
闪电侠:对,对呃,不,还是我我不是说过你要考虑这个版本吗
于:我也想,但是你一说起技术就一直在自言自语我能怎么做呢
修卡:呃,对不起,对不起,那么请总结一下我们的线程池。
摘要
于:嗯,好的,首先它的建造方法是这样的。
publicFlashExecutor...///省略一些参数来检查这一点。corepowersize = corepowersizethis . maximum poolsize = maximum poolsize,this . work queue = work queue,this . keepAliveTime = unit . tonanos,this . thread factory = thread factory,this.handler = handler
这些参数是
Corepoolsize:核心线程的数量
Int maximumPoolSize:线程的最大数量
Long keepAliveTime:非核心线程的空闲时间。
单位Unit:空闲时间的单位。
阻塞队列:任务队列
线程工厂线程工厂:线程工厂
RejectedexecutionHandler处理程序:拒绝策略
整个任务提交过程是
闪电侠:不错不错,不过是你自己总结出来的现在需要我告诉你什么是线程池吗
于:天啊,我才发现,这好像就是我一直搞不清楚的线程池的参数和原理!
Flash:没错,而且最后一个版本代码的构造方法是ThreadPoolExecutor最长的构造方法,在Java面试中经常被测试参数名称没有改变
于:哇,太好了!忘了当初想干什么了,嘻嘻。
修卡:哈哈,不知不觉中学会技术很酷,对吧该吃晚饭了你想去山西面馆吗
于:哦,我不喜欢那家商店餐桌的颜色也许下次吧
闪电侠:哦,好的。
。