大家有没有在面试中被问过线程池的使用,或者说实现?
作为Java开发,多多少少都会用到线程池这个工具。
今天我们就来学习一下线程池跟跟普通的线程调用有什么区别。
线程的使用
线程是什么,这个这里就不做解析了。不同的同学可以谷歌一下:《线程与进程》。
下面我们来简单讲下什么情况下会使用Thread这个类。
例如我们写一个Client/Server 的项目。
作为Server,需要不断的监听client发送的请求。这时候,如果应用是单线程的,那么每次对client的请求都需要完成相关应答之后才能开始监听下一个client的请求。
下面是单线程版本对应的伪代码:
while(true){
Client c = listen_to_client();
Request req = c.getRequest();
Response rsp = doSomeWokr(req);
sendRsp(rsp);
}
如果我们改进一下,用多线程版本来实现。这样server的master线程就不用处理实际的应答,只需要用来监听新的请求即可。
class Worker{
Client c ;
Worker(Client c){
this.c = c;
}
public void run(){
Request req = c.getRequest();
Response rsp = doSomeWokr(req);
sendRsp(rsp);
}
}
while(true){
Client c = listen_to_client();
new Worker(c).start();
}
通过改进之后,这个应用的资源利用率就大大提升了很多。Client不需要做无谓的等待,尤其是在一些计算密集或者IO操作的情况下。
ThreadPool
下面我们开始讲正题。
首先我们理解一下这个pool。线程与线程池最大的区别就在这个pool上面。
通常情况下,我们开启一个异步任务的,会新建一个线程。但是创建和销毁Java线程的开销,还是不可小看。
64位的Linux机器,stack的大小默认是1M,这个大小可以通过-Xss来配置,例如-Xss256k。但是对于平时我们使用的MVC 框架类型的应用,建议使用默认值就可以了。
于是这个池就起作用了。Java可以复用之前已经创建过的线程,进行再利用。这样就不需要每次都新开一个线程。
剖析如何复用
现在我们来看下ThreadPoolExecutor的核心源码,看下到底是怎么回事。
先从入口方法execute看
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
咋一看,好像没看出什么东西。
这里的核心代码应该在addWorker,offer和reject上面。因为command 的流向是往这个方向走。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
...
...
//省略部分非核心代码
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//省略部分代码
if (workerAdded) {
//如果Worker添加成功就开启线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
上面的addWorker 中,核心代码就是如上所示。
firstTask 参数就是要执行的runnable,只要进来addWorker 方法,线程池中就要新建一个Woker Thread 来执行实际的任务。
下面我们再来看下这个Worker 的代码:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
一个实现了runnable接口的类。那其实他主要的工作就是在run中。
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
这个run 的方法只是委托给了runWorker 的方法。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
...
//省略部分代码
...
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
Thread wt = Thread.currentThread();获取当前线程
Runnable task = w.firstTask;获取第一个执行任务。
while (task != null || (task = getTask()) != null) 如果当前线程的task队列不为空,则循环执行所有的runanble 任务。
从这段核心代码可以看出,worker的主要工作就是从task queue中不断遍历出所有的任务,然后执行。
这里的getTask 就是一个队列,从execute方法中,进来的runnable任务,都会被分到一个个的线程当中。
具体怎么被分配,我们留到下一期再讲,今天就讲到这里。
扫描以下二维码,加入技术群交流学习,备注“加群”:
image