必备技能——使用线程池来初始化缓冲队列
2019-07-16 本文已影响0人
AmosZhu
在我们的业务场景中,经常会遇到一些需要在系统启动时就需要手动缓存一下线程,方便我们去使用已有的线程去处理一些业务,降低系统资源的消耗等,今天我们就来讲解下Java开发必备技能,使用线程池初始化缓冲队列
ServletListenerRegistrationBean
我们用来演示的项目是基于SpringBoot框架,在框架中我们需要向web容器中注册监听器,可以使用ServletListenerRegistrationBean
来实现,具体的代码如下:
/**
* Copyright © 2018 五月工作室. All rights reserved.
*
* @Package com.amos.common.config
* @ClassName ServletListenerRegistrationConfig
* @Description 在容器启动的时候,注册自定义的Listener
* @Author Amos
* @Modifier
* @Date 2019/7/14 16:41
* @Version 1.0
**/
@Configuration
public class ServletListenerRegistrationConfig {
/**
* 注册自定义的Bean
*
* @return
*/
@Bean
public ServletListenerRegistrationBean registrationBean() {
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
servletListenerRegistrationBean.setListener(new InitThreadLocalPoolListen());
return servletListenerRegistrationBean;
}
}
其中InitThreadLocalPoolListen
就是我们自定义的监听器,可以在通过该监听器实现我们需要处理的逻辑
线程池
线程池:创建一些线程,使用池化技术来存储这些线程。
使用线程池有如下的好处:
- 降低资源消耗
可以利用重复已创建的线程降低线程创建和销毁的消耗
- 提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性
使用线程池可以进行统一分配、调优和监控
线程池的创建其实说到底就一种实例化的方法,然后通过不同的参数来实现不同效果的
/**
* 常用的五个参数如下:
* 初始化线程池 这里我们不使用Executors.newFixedThreadPool()方式,该种方式不推荐使用,
* 主要是因为默认允许的队列的长度是Integer.MAX_VALUE,可能会造成OOM
* 第一个参数:corePoolSize: 线程中核心线程数的最大值(能同时运行的最大的线程数)
* 第二个参数:maximumPoolSize: 线程池中线程数的最大值
* 第三个参数:keepAliveTime: 线程存活时间
* 第四个参数:unit:时间单位
* 第五个参数:BlockingQueue: 用于缓存任务的队列 这里使用 ArrayBlockingQueue 这个是有界队列
*/
private ExecutorService threadPool = new ThreadPoolExecutor(this.corePoolSize, this.maximumPoolSize,
this.keepAliveTime, TimeUnit.SECONDS,
new ArrayBlockingQueue(this.corePoolSize));
在实际的应用中,我们将线程池使用单例的模式来实现线程池的单例,这里我们使用静态内部类的方式来实现单例模式,做到线程的绝对安全
/**
* 使用静态内部类来实现单例的模式(绝对的线程安全)
*/
private static class Singleton {
/**
* 私有的静态变量,确保该变量不会被外部调用
*/
private static RequestThreadPool requestThreadPool;
/**
* 静态代码块在类初始化时执行一次
*/
static {
requestThreadPool = new RequestThreadPool();
}
/**
* 静态内部类对外提供实例的获取方法
*
* @return
*/
public static RequestThreadPool getInstance() {
return requestThreadPool;
}
}
使用静态内部类的方式来实现单例模式主要有以下的优点:
- 外部内加载的时候,不需要立即加载内部类,内部类不被加载,就不会初始化,故而不占用内存
- 当
getInstance
被调用时,才会去初始化实例,第一次调用getInstance
会导致虚拟机加载实例,这种方法不仅能确保线程的安全,也能保证单例的唯一性
结合上述的线程池实例化和单例模式,我们整合代码如下:
/**
* Copyright © 2018 五月工作室. All rights reserved.
*
* @Package com.amos.common.thread
* @ClassName RequestThreadPool
* @Description 使用线程池来管理线程,该线程池必须是单例的
* @Author Amos
* @Modifier
* @Date 2019/7/14 16:47
* @Version 1.0
**/
@Component
public class RequestThreadPool {
/**
* 核心线程数
*/
@Value("${request.queue.corePoolSize:10}")
private Integer corePoolSize;
/**
* 线程池最大线程数
*/
@Value("${request.queue.maximumPoolSize:20}")
private Integer maximumPoolSize;
/**
* 线程最大存活时间
*/
@Value("${request.queue.keepAliveTime:60}")
private Long keepAliveTime;
/**
* 初始化线程池 这里我们不使用Executors.newFixedThreadPool()方式,该种方式不推荐使用,
* 主要是因为默认允许的队列的长度是Integer.MAX_VALUE,可能会造成OOM
* 第一个参数:corePoolSize: 线程中核心线程数的最大值(能同时运行的最大的线程数)
* 第二个参数:maximumPoolSize: 线程池中线程数的最大值
* 第三个参数:keepAliveTime: 线程存活时间
* 第四个参数:unit:时间单位
* 第五个参数:BlockingQueue: 用于缓存任务的队列 这里使用 ArrayBlockingQueue 这个是有界队列
*/
private ExecutorService threadPool = new ThreadPoolExecutor(this.corePoolSize, this.maximumPoolSize,
this.keepAliveTime, TimeUnit.SECONDS,
new ArrayBlockingQueue(this.corePoolSize));
/**
* 构造器私有化,这样就不能通过new来创建实例对象
* <p>
* 类实例化的时候 ,初始化队列的大小,并且绑定队列和线程池以及队列与线程的关系
* <p>
* 初始化指定数量的队列
*/
private RequestThreadPool() {
/**
*缓存队列集合来管理所有的缓存队列
*/
RequestQueue requestQueue = RequestQueue.getInstance();
for (int i = 0; i < this.corePoolSize; i++) {
/**
* 缓存队列使用Request 接口来作为泛型,将可以将队列的类型添加定义,同时也可以通过多态的特性来实现子类的扩展
* 目前Request只是定义,业务可以之后实现
*/
ArrayBlockingQueue<Request> queue = new ArrayBlockingQueue<>(this.corePoolSize);
requestQueue.add(queue);
this.threadPool.submit(new RequestThread(queue));
}
}
/**
* 使用静态内部类来实现单例的模式(绝对的线程安全)
*/
private static class Singleton {
/**
* 私有的静态变量,确保该变量不会被外部调用
*/
private static RequestThreadPool requestThreadPool;
/**
* 静态代码块在类初始化时执行一次
*/
static {
requestThreadPool = new RequestThreadPool();
}
/**
* 静态内部类对外提供实例的获取方法
*
* @return
*/
public static RequestThreadPool getInstance() {
return requestThreadPool;
}
}
/**
* 请求线程池类对外提供获取实例的方法 由于外部类没有RequestThreadPool的实例对象,所以除了该方法,外部类无法创建额外的RequestThreadPool对象
*
* @return
*/
public static RequestThreadPool getInstance() {
return Singleton.getInstance();
}
}
其中RequestQueue
如下:
/**
* Copyright © 2018 嘉源锐信. All rights reserved.
*
* @Project: rabbitmq
* @ClassName: RequestQueue
* @Package: com.amos.common.request
* @author: zhuqb
* @Description: 请求的队列
* <p/>
* 这里需要使用单例模式来确保请求的队列的对象只有一个
* @date: 2019/7/15 0015 下午 14:18
* @Version: V1.0
*/
public class RequestQueue {
/**
* 构造器私有化,这样就不能通过new来创建实例对象
* 这里构造器私有化 这点跟枚举一样的,所以我们也可以通过枚举来实现单例模式,详见以后的博文
*/
private RequestQueue() {
}
/**
* 内存队列
*/
private List<ArrayBlockingQueue<Request>> queues = new ArrayList<ArrayBlockingQueue<Request>>();
/**
* 私有的静态内部类来实现单例
*/
private static class Singleton {
private static RequestQueue queue;
static {
queue = new RequestQueue();
}
private static RequestQueue getInstance() {
return queue;
}
}
/**
* 获取 RequestQueue 对象
*
* @return
*/
public static RequestQueue getInstance() {
return Singleton.getInstance();
}
/**
* 向容器中添加队列
*
* @param queue
*/
public void add(ArrayBlockingQueue<Request> queue) {
this.queues.add(queue);
}
}
RequestThread
的代码如下:
/**
* Copyright © 2018 嘉源锐信. All rights reserved.
*
* @Project: rabbitmq
* @ClassName: RequestThread
* @Package: com.amos.common.thread
* @author: zhuqb
* @Description: 执行请求的工作线程
* <p/>
* 线程和队列进行绑定,然后再线程中处理对应的业务逻辑
* @date: 2019/7/15 0015 下午 14:34
* @Version: V1.0
*/
public class RequestThread implements Callable<Boolean> {
/**
* 队列
*/
private ArrayBlockingQueue<Request> queue;
public RequestThread(ArrayBlockingQueue<Request> queue) {
this.queue = queue;
}
/**
* 方法中执行具体的业务逻辑
* TODO
*
* @return
* @throws Exception
*/
@Override
public Boolean call() throws Exception {
return true;
}
}
具体的代码详见:
rabbitMQ