今天要造的轮子是 线程池 。
所有的代码已经上传到了github上,大家可以自行下载:github.com/iongst/java…
池化技术这个东西已经不需要多说了,逻辑很简单,正常情况下线程的创建是一个比较耗费资源的事情,能不能用完之后放在某个地方,方便下次继续使用呢? 再简单点就是个缓存而已。
那么我们今天要写的这个小工具就是线程池,本质上和ThreadPoolExcutor 没啥区别,只能说一模一样。
只不过我们会增加一些比较好玩的(其实没啥用)的功能,写代码的目标就 我喜欢就行。
大致希望完成的过程应该包含以下内容:
- 根据不同的配置文件,去解析读取配置信息;
- 通过读取的配置信息创建线程池对象;
- 包含的配置参数有:线程池数量、最大数量、空闲线程存活时间、空闲线程存活时间单位、工作队列、线程工厂、拒绝策略;
- 在线程池类中实现线程的创建逻辑,可以在构造函数中初始化核心线程,并保存到线程集合中。
- 实现执行任务的方法execute(),当有新任务到来时,首先判断当前运行的线程数是否达到核心线程数,如果没有达到,就创建一个新线程来执行任务。
- 如果运行的线程达到了核心线程数,则将任务加入阻塞队列。如果队列已满,则创建新线程来处理任务,但最大线程数不能超过预定的值。
- 实现线程的关闭与销毁方法,也可以考虑实现定时清理空闲线程的功能。
- 线程执行完任务后,需要正确的重置其状态,方便线程下次被重用执行任务。
- 可以通过继承ThreadFactory来实现自定义的线程工厂类,用来创建线程。
- 设置线程的异常处理,避免线程异常后不可用。
今天要完成的是第一部分,将创建线程池的各个配置项通过配置文件读取进来,然后创建一个对应的Config对象。
大致需要完成的功能如下:
这里涉及到的几个问题:
- 1: 配置文件中需要配置的信息有哪些?
- 2: 不同的配置信息读取方式不同;
- 3: 需要保证用户在使用的时候简单
配置文件中的信息
我们仿照线程池中的内容,全部给他挪过来。
## 核心线程个数
corePoolSize=10
## 最大线程池个数
maxNumPoolSize=20
## 空闲线程存活时间
keepAliveTime=2
## 时间单位
unit=seconds
## 线程池任务队列
workQueue=linked
## 拒绝策略
handler=AbortPolicy
我们这里少写了一个对应的创建线程的工厂,这个工厂我们放到后面创建的时候手动传入。
针对与这些参数,有几个稍微有点问题的我们说明一下。
针对于时间单位、线程池的任务队列以及拒绝策略我们使用枚举来完成。动态的创建对应的对象。但是因为比如像时间单位和任务队列我们也可以偷懒一下,通过写一些判定条件然后创建指定的对象。等到后面再进行修改。
举个例子:
对于时间单位内容,我们编写一个方法来获取对应的单位值。
// 根据配置的值来创建对应的单位对象,这里直接使用TimeUnit
private TimeUnit getUnit(String value){
if ("seconds".equalsIgnoreCase(value))
return TimeUnit.SECONDS;
else if ("milliseconds".equalsIgnoreCase(value))
return TimeUnit.MILLISECONDS;
else
return TimeUnit.SECONDS;
}
对于拒绝策略,我们可以直接配置一个枚举,因为这一部分我们在代码中可以自己完成:
//当线程池的任务超出线程池队列可以存储的最大值之后,执行的策略
public enum Policy {
AbortPolicy, //拒绝并抛出异常
CallerRunsPolicy,//使用当前调用的线程来执行此任务
DiscardOldestPolicy,//抛弃队列头部(最旧)的一个任务,并执行当前任务
DiscardPolicy;//忽略并抛弃当前任务
}
不同的配置信息读取方式不同
我们这里设置了三种对应的配置文件,当然也可以再增加一种,比如json ;
因为不同的配置文件读取方式不同,所以我们需要创建对应的不同的解析对象;
不同文件的解析方式如下:
- properties文件可以使用Properties类加载
- xml文件可以使用DOM,SAX等方式解析
- yml文件可以使用SnakeYAML等库解析
举例,如果我们解析properties文件,则可以通过下面这个代码完成:
// 通过parser方法来封装一个解析方法 这个方法最后将解析的值封装到Config对象中
public Config parser() {
properties = new Properties();
Config config = new Config();
try {
properties.load(this.getClass().getClassLoader().getResourceAsStream(configFilePath));
config.setCorePoolSize(Integer.parseInt(getValue("corePoolSize")));
config.setMaxNumPoolSize(Integer.parseInt(getValue("maxNumPoolSize")));
config.setKeepAliveTime(Long.parseLong(getValue("keepAliveTime")));
config.setUnit(getUnit(getValue("unit")));
config.setHandler(Policy.valueOf(getValue("handler")));
config.setWorkQueue(getQueue(getValue("workQueue")));
} catch (IOException e) {
throw new IllegalArgumentException("file path is error:"+configFilePath);
} catch (IllegalArgumentException e){
throw e;
}
return config;
}
需要保证用户在使用的时候简单
保证用户使用简单,目前想来的过程大致分为以下几步:
这里将所有的解析对象全隐藏起来。使用起来如下:
第一步直接获取到一个解析配置对象
这个对象是一个抽象类,它的子类对象就是针对不同的文件格式创建的解析对象;
内部实现细节就是通过后缀名称确定解析对象,并且返回。
// 通过文件名称直接获取到一个解析配置对象
ParserConfig load = ParserConfigSource.load("config.properties");
内部的实现细节如下:
// 创建对应的解析对象
public static ParserConfig load(String configFilePath){
// 获取后缀
String extension = getFileExtension(configFilePath);
if(extension==null||extension.isEmpty()) {
throw new IllegalArgumentException("Rule config file format is not supported: " + configFilePath);
}
// 通过文件名称和后缀 创建对应的对象 放入缓存中
initCache(configFilePath,extension);
// 从缓存中直接获取
return CACHE.get(extension);
}
第二步 通过解析配置对象获取到解析之后的实例对象
// 将数据映射到实体对象中,内部如何实现细节也不需要管了
Config parser = load.parser();
这里的parser方法也是抽象方法,实际会根据load的对象是那个解析对象来具体解析。
// 抽象类中定义的解析方法
abstract Config parser();
// 对于properties中的解析规则如下
public Config parser() {
properties = new Properties();
Config config = new Config();
try {
properties.load(this.getClass().getClassLoader().getResourceAsStream(configFilePath));
config.setCorePoolSize(Integer.parseInt(getValue("corePoolSize")));
config.setMaxNumPoolSize(Integer.parseInt(getValue("maxNumPoolSize")));
config.setKeepAliveTime(Long.parseLong(getValue("keepAliveTime")));
config.setUnit(getUnit(getValue("unit")));
config.setHandler(Policy.valueOf(getValue("handler")));
config.setWorkQueue(getQueue(getValue("workQueue")));
} catch (IOException e) {
throw new IllegalArgumentException("file path is error:"+configFilePath);
} catch (IllegalArgumentException e){
throw e;
}
return config;
}
为了完成上面的需求,大致设计类图如下:
通过上面的配置,我们基本上就完成了第一部分内容。
如果上面的代码对你来说有点启发,那么不要忘记点个赞、点个在看哦。
版权声明:本站所有文章除特别声明外,均采用 CC BY-NC-ND 4.0
转载请注明来自 kengwanglaoxue
当前文章作者名:kengwanglaoxue
当前文章标题:自己写个小工具,造个轮子 (手写线程池01)
当前文章地址:997coder.com/thread01.ht…