本节会从软件开发功能需求的角度引出 Java线程池
的功能,然后我们会自己实现这个功能,最后再分析JUC Executors 的源码。
Doug Lea < Java Concurrcy In Practice > 第六章 任务执行 中描述:
大多数的应用程序是围绕 任务
进行组织的。任务就是抽象的、独立的工作单元。把应用程序的工作分离为一个个的任务,可以简化应用程序(分而治之的思想)。
服务器应用程序应该具有:
1、良好的吞吐量和快速的响应能力;
2、在负载越来越大时,性能会平缓的下降,而不是直接导致简单粗暴的程序崩溃
;
一个线程串行执行任务 和 每个任务开启一个线程都存在一些问题。
一个线程串行执行任务的问题:应用程序吞吐量和相应能力都很差。
每个任务开启一个线程执行(thread-per-task)的问题:
1、应用程序需求
功能需求:服务器应用程序会不断接收客户端的请求,收到请求后,应用程序对请求进行处理。
非功能需求:支撑高并发和高可用
2、应用程序设计
根据功能需求的描述,我们抽出几个名词和动词:
名词:服务器应用程序;客户端的请求;
动词:接收请求;对请求进行处理;
伪代码:
class ServerApp{
boolean receive(Request req);
boolean process(Request req)
}
class Request{
}
上面的实现是最直接的实现方式,但我们在写程序的时候还要考虑程序的给功能需求。
根据文章开头描述,我们不能使用上面的这种实现方式,这种是第一种串行的执行方式。
采取第二种方式,伪代码如下:
class ServerApp{
boolean receive(Request req){
new Thread(new Runnalbe(){
public void run(){
process(req)
}
}).start();
}
boolean process(Request req){}
}
class Request{
}
第二中方式也是存在问题的
我们采用线程池的方式 ,伪代码如下:
class ServerApp{
Thread[] threads;
ServerApp(int nThreads){
threads = new Thread[nThreads];
}
boolean receive(Request req){
req 绑定到 threads[?];
threads[?]中调用process(req)
}
boolean process(Request req){}
}
class Request{
}
上面的伪代码需要进一步细化:
1、需要将请求(任务)和线程进行绑定,这样才能将任务分配给线程
2、大部分的情况下,任务是比线程数多的,所以我们需要一个地方来暂存任务请求,然后空闲的线程从这个地方来取出任务进行执行
我们尝试实现一下:
/**
* 1、单独定义一个Thread,方便和Request进行绑定
*/
class WorkerThread extends Thread{
Request req;
public void run(){
req.execute()
}
void setRequest(Request req){
this.req = req;
}
}
class ServerApp{
Thread[] workerThreads ;
/* 2、暂存任务的地方*/
BlockingQueue<Request> requestQueue;
ServerApp(int nThreads){
requestQueue = new LinkedBlockingQueue();
workerThreads = new WorkerThread [nThreads];
}
boolean receive(Request req){
if(有空闲的WorkerThread ){
空闲的WorkerThread.setRequest(req)
}else{
requestQueue.add(req);
}
}
boolean process(Request req){}
}
class Request{
void execute();
}
上面的伪代码,还有需要很多细节和设计上的问题需要解决:
1、Request 是自己定义的类,其实应该是一个客户端实现的接口,只有一个execute方法,作为一个Java 开发人员我们思考一下Java提供的API里面有这样的接口么? 很明显 Runnalbe
接口便是。
2、我们的需要实现的线程池逻辑和业务有关系么?是不是可以独立出来做成通用的功能类?
3、WorkerThread 的run方法是不是应该循环从queue中取出任务执行?
上面就是我们设计的过程,到这步我们发现要考虑很多实现上的细节了,所以我们从现在开始该进行编码了,并在编码的过程中不断的进行重构。
在实现代码前我们想想线程池的命名:线程池的作用是用来执行任务,所以我们将其命名为 ThreadPoolExecutor
public class ThreadPoolExecutor {
private final BlockingQueue<Runnable> taskQueue;
private List threads;
public ThreadPoolExecutor(int poolSize) {
this.taskQueue = new LinkedBlockingQueue<Runnable>();
this.threads = new ArrayList(poolSize);
for(int i=0;i<poolSize;i++){
WorkerThread workerThread = new WorkerThread();
threads.add(workerThread);
workerThread.start();
}
}
/**
* 执行任务
* @param task
* @return
*/
public boolean execute(Runnable task) {
boolean isOfferd = taskQueue.offer(task);// 队列满?return false:return true
return isOfferd;
}
class WorkerThread extends Thread {
public void run() {
while (true) {
try {
Runnable task = taskQueue.take();
System.err.println(taskQueue.size());
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}