Java的线程池,维护了一个线程池,每一个新的任务都会提交到线程池,由线程池进行调度和资源释放,这样的好处:
1.通过线程池,可以限制线程创建的数量,当创建许多的任务时,任务需要在线程池中进行排队。
2.通过线程池,统一管理任务调度以及异常的策略处理。
Java提供了这些线程池,不同的线程池,使用的场景不同,这里重点不是介绍各种线程池的特点,而是Java的线程池是怎么设计的,如何实现重用。Java通过Executors提供四种线程池,分别为:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*线程池Demo*/
public class ThreadPoolDemo {
public static void main(String[] args) {
// <1> 创建了一个FixedThreadPool线程池,指定线程池的大小为10
ExecutorService executorService = Executors.newFixedThreadPool(10);
// <2> 循环生成20个线程任务,提交到FixedThreadPool线程池中
/*线程池,打印线程名字和i的值*/
for (int i = 0;i < 20;i++){
int finalI = i;
// <3> 任务中主要打印了线程的name和i的值。
executorService.execute(()-> System.out.println(Thread.currentThread().getName()+"->" + finalI));
}
executorService.shutdown();
}
}
上面的例子主要做了这几件事:
运行例子的执行结果如下图:
有这个结果可以看pool-1-tread-*
这个就是线程池中线程的名字,因为我们例子中创建了20个任务,而线程池中执行任务的线程我们只定义了10个,所以如运行的结果所示,部分线程池中的线程复用了。这就是线程池的特性,可以实现线程的复用,不用为每一个线程任务单独创建线程。
其实根据上面的运行结果我们可以进行一下推测,线程池是怎么复用的。根据上面的结果,可以发现,创建的20个线程任务,提交到了线程池,执行时永远都只会打印了线程池中的10个执行线程的名字。说明线程池中,有一组10个线程,不断的在运行着,当有线程任务提交到线程时,线程池空闲的线程就会将提交的任务放到当前执行。提交进来的线程任务,虽然是实现了runnable
接口,但是在线程池中它们不会作为线程进行调用,而是被当做了简单一个Java对象,在执行任务中,调用的是线程任务的run方法而已;下面我们通过阅读源码来看看。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//<1> 如果运行的线程少于corepoolsize,请尝试以给定的命令作为第一个任务启动新线程。
if (workerCountOf(c) < corePoolSize) {
//<1.1> 对addworker的调用以原子方式检查runstate和workercount,因此可以通过返回false来防止在不应该添加线程时出现错误警报。
if (addWorker(command, true))
return;
c = ctl.get();
}
//<2> 如果任务可以成功排队,那么我们仍然需要再次检查是否应该添加线程(因为自上次检查以来已有的线程已死亡),或者池是否在进入此方法后关闭。
if (isRunning(c) && workQueue.offer(command)) {//将任务加入队列中
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
//<2.1>因此,我们重新检查状态,如果停止,则回滚排队
reject(command);
else if (workerCountOf(recheck) == 0)
//<2.2> 如果当前的线程池没有线程,则启动新线程。
addWorker(null, false);
}
//<3> 如果无法将任务排队,则尝试添加新线程。如果失败了,我们知道我们被关闭或饱和,所以拒绝任务。
else if (!addWorker(command, false))
reject(command);
}
ThreadPoolExecutor
的一些默认的变量,用于标识该线程池的状态,在该类文件的前面:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//记录位数,32-3=29位,整型是4个字节,32位,为下面的运行状态留了3位用于表示。
private static final int COUNT_BITS = Integer.SIZE - 3;
//默认的线程池的最大线程数,根据这个计算则最大线程数为:2^29 - 1个线程。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 运行状态,使用高位来标识,这也是前面COUNT_BITS减去3的原因。
private static final int RUNNING = -1 << COUNT_BITS;//101...标识RUNNIG
private static final int SHUTDOWN = 0 << COUNT_BITS;//000...标识SHUTDOWN
private static final int STOP = 1 << COUNT_BITS;//001...标识STOP
private static final int TIDYING = 2 << COUNT_BITS;//010...标识TIDYING
private static final int TERMINATED = 3 << COUNT_BITS;//011...标识TERMINATED
workerCountOf和isRunning
分别用于计算当前线程池的大小和运行状态:
// c是当前的线程数,c & CAPACITY标识按位与运算,所以最终保留的是c对应的位数,即当前线程池中的工作线程数。
private static int workerCountOf(int c) { return c & CAPACITY; }
// c是当前的线程数,c & ~CAPACITY表示c和CAPACITY取反值得按位与运算,~表示取反,然后再与c进行按位与运算,因此跟上面的 workerCountOf的运算时相反的,这里运算后的保留的时高三位,所以表示的时当前线程池对应的运行状态。
private static int runStateOf(int c) { return c & ~CAPACITY; }
addWorker
是线程中添加工作线程的核心代码,添加工作线程的源码如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//<1> 尝试增加ctl的值,如果满足条件则新增worker的个数
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// <1.1> 检查线程池的状态和workQueue和firstTask是否为null
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//<1.2> 如果core为true,则工作线程池与corePoolSize值比,否则与最小线程数比较 if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//<1.3> 尝试更新worker的总数,如果失败则调到retry:进行重试
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//<2.1> 新建Worker实例
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//<2.2> 加锁
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
//<2.3> 判断线程池的状态
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//<2.3.1> 将worker加入线程池的工作线程中
workers.add(w);
int s = workers.size();
//<2.3.2> 更新线程池的当前最大线程数据中和工作线程添加的标识workerAdded
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
主要完成了几步:
1.尝试增加ctl的值,如果满足条件则 尝试更新worker线程的总数,如果失败则调到retry:进行重试
2.新建Worker实例,加锁,判断线程池状态正常则向workers中添加新的工作线程worker.
Worker
类的实现:
//Worker类的的构造函数
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;//我们提交的任务作为了Worker的一个成员变量存在
this.thread = getThreadFactory().newThread(this);//实例化一个线程对象。
}
Worker类中,runWorker方法就是线程池中的工作线程最核心的代码,也就是是Worker实例执行我们提交的任务线程的代码的地方,
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;//记住,这个就是我们提交的线程任务对象。
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 如果Worker中需要处理的Task不为空,则继续往下执行。
while (task != null || (task = getTask()) != null) {
//<1> 工作线程加锁,确保只处理当前的任务
w.lock();
//<2> 如果池正在停止,请确保线程已中断;否则,请确保线程未中断。这需要在第二种情况下重新检查,以便在清除中断时处理shutdownnow race
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//<3> 这就是我们的任务的实际执行,只是对提交给线程池的Runnable,其实最终在工作线程中,对这个Runnable的任务只是当做了一个简单的对象,调用的run方法而不是start方法。
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);
}
}
到此,整个线程池的核心过程就讲完了。
回到最开始的那个例子,Executors.newFixedThreadPool(10);
创建的10个线程是被重用的;service.execute(()-> System.out.println(Thread.currentThread().getName()+"->" + finalI));
向线程池中添加的任务,线程池是并不会调用你提交的Runable作为Thread进行调用start方法。
线程重用的核心是,它把Thread#start()
给屏蔽起来了(一定不要重复调用),然后线程池它自己的工作线程Worker封装了对于你提交的任务的调用,只是调用了你提交的任务的Runnable#run()
方法,循环在跑,跑的过程中不断检查我们是否有新加入的子Runnable对象,如果工作线程不存在,则先创建工作线程否则就直接调一下工作线程的run()
方法;其实就类似于一个大的run()
把其它任务即run()#1,run()#2,run()#3,...
给串联起来了,基本原理就这么简单,当然具体也没这个描述那么简单,但是他的实现大概就是上述描述的过程。