前言
现在多数服务器都有多个CPU,这为多线程并发执行任务提供了良好的硬件支持,在开发中,我们也会根据具体业务尝试使用多线程编程来提高业务执行的效率。那么在java8中如何正确的使用多线程?如何减少资源的消耗让任务执行更高效呢?
线程池
在java中创建、销毁多线程都是要破费很多资源的,所以在使用中应该避免自己创建多线程去执行任务,而是使用线程池来处理。我们常用的就是ExecutorService,它是执行多线程的服务,而ThreadPoolExecutor就是它的一个实现类。在Executors类中,提供了创建线程池的各种静态方法。
我们先来看看Executors类中都有哪些方法,以下是此类的结构,可以看到此类不能创建实例只提供静态方法。
其中最常使用的有:
- newFixedThreadPool(int tnum)创建一个固定线程数量的线程池,例如:tnum=3表示最多有三个线程同时工作。
- newSingleThreadExecutor 创建单线程的线程池,也就是说它只会有一个线程来工作,从而可以保证所有任务按照指定优先级顺序执行。
- newCachedThreadPool创建一个可缓存的无界(不超过int的max_value)线程池,如果线程池长度超过处理任务,可以回收空闲线程,否则新建线程来处理此次任务。
- newScheduledThreadPool延时处理线程池,支持定时执行或定时周期执行任务。
上面的四种线程池除newScheduledThreadPool是由ScheduledThreadPoolExecutor类实现的功能,其它的都是由ThreadPoolExecutor实现或间接由ThreadPoolExecutor实现。下面我们来看看ThreadPoolExecutor有何神奇之处。
ThreadPoolExecutor实现原理
构造函数与参数
首先我们来看一下ThreadPoolExecutor类的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其它的构造函数都是由此生成,只是传入了一些默认值,其中各参数的意义:
- corePoolSize,核心线程数,表示线程池中最大可以保留多少个线程来处理任务,如果任务数量小于此值,则会新创建一个工作线程来处理此任务。
- maxinumPoolSize,最大线程数(注意没有"核心"二字),表示线程池中最多可以创建多少个线程。当需要处理的任务很多时,maxinumPoolSize减去corePoolSize>0的线程可以看作是“游离线程”,在处理完任务后可以被回收掉。
- keepAliveTime,这个不太好理解,它是任务的最大空闲时间,超过这个时间没有取到任务就会销毁此工作线程。
- unit,这个就是时间的单位
- workQueue,工作线程阻塞队列,当任务大于核心线程数后,会将任务处理加入此队列排队等待处理。
- threadFactory,创建工作线程的工厂类
- handler,当任务数量超过了maxinumPoolSize就是拒绝此任务,使用hanlder处理策略。实现RejectedExecutionHandler此接口的类(jdk中此接口的实现类都是TheadPoolExecutor内部类),共有四种策略:
- AbortPolicy,直接拒绝处理,并抛出RejectedExecutionException异常。
- DiscardPolicy,直接忽略(丢弃)。
- CallerRunsPolicy,直接启动运行传入的任务线程。
- DiscardOldestPolicy,丢弃最老的那个任务,来执行此任务。
源码解读
我们一般的使用过程是:
1.先创建一个线程池
private static ExecutorService executorService = Executors.newFixedThreadPool(3);
2.然后使用此线程池来执行任务线程
executorService.execute(new Runnable() {
public void run() {
///需要执行的业务
}
});
我们进入execute方法,看一看它是如何执行的
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
///下面这段英文,说明了执行的步骤
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
//首先判断工作线程数是否小于核心线程数,如果是则新创建一个worker来执行此任务。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//否则,如果线程池没有shutdown就把任务加入到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果此时再次判断,已经shutdown了就从队列中删除并拒绝执行任务(拒绝策略上面已经提到)
if (! isRunning(recheck) && remove(command))
reject(command);
//如果此时工作线程为0,就加入并启动一个空的工作线程(为什么是一个空的工作线程呢?
//因为上面已经把任务已经加入到队列当中了,只需要启动一个工作线程去执行队列中的任务即可)
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果加入队列不成功(这个有可能是传入的阻塞队列有大小限制,
//newFixedThreadPool直接使用的是new LinkedBlockingQueue<Runnable>()。)
//就尝试新添加工作线程来执行此次任务。
else if (!addWorker(command, false))
reject(command);
}
可以看到,上面的源码列出了执行的大概流程,其中有些是方法的封装,例如reject(command),这个就是执行拒绝策略的,而另一个就是addWorker,添加一个工作线程,我们来看看它干了哪些工作
/**
如果英文好的同学可以直接看这里,哈哈
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started, running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked. If the thread
* creation fails, either due to the thread factory returning
* null, or due to an exception (typically OutOfMemoryError in
* Thread.start()), we roll back cleanly.
*
* @param firstTask the task the new thread should run first (or
* null if none). Workers are created with an initial first task
* (in method execute()) to bypass queuing when there are fewer
* than corePoolSize threads (in which case we always start one),
* or when the queue is full (in which case we must bypass queue).
* Initially idle threads are usually created via
* prestartCoreThread or to replace other dying workers.
*
* @param core if true use corePoolSize as bound, else
* maximumPoolSize. (A boolean indicator is used here rather than a
* value to ensure reads of fresh values after checking other pool
* state).
* @return true if successful
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry://一个for循环标记
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//如果运行状态正常
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//如果工作线程数量大于核心线程数量或最大线程数量
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//循环原子添加工作线程数量
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 {
//把任务封装成一个worker(也是一个线程)
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//把工作线程worker添加到workers的Set中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s; //重置最大工作线程数
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); //如果添加到Set成功就启动此worker
workerStarted = true; //标记启动成功
}
}
} finally {
if (! workerStarted) //如果没有启动成功,标记此worker为失败
addWorkerFailed(w);
}
return workerStarted;
}
可以看到addWorker执行的重要部分就是,把用户传入的任务封装成一个worker并且把这个worker添加到一个Set当中并启动此worker。我们来看一下worker类的实现
private final class Worker
extends AbstractQueuedSynchronizer//同步的队列
implements Runnable//也是一个线程
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//重点,当上面的执行t.start时就会运行此run
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
.........
}
/**
* Main worker run loop. Repeatedly gets tasks from queue and
* executes them, while coping with a number of issues:
*
* 1. We may start out with an initial task, in which case we
* don't need to get the first one. Otherwise, as long as pool is
* running, we get tasks from getTask. If it returns null then the
* worker exits due to changed pool state or configuration
* parameters. Other exits result from exception throws in
* external code, in which case completedAbruptly holds, which
* usually leads processWorkerExit to replace this thread.
*
* 2. Before running any task, the lock is acquired to prevent
* other pool interrupts while the task is executing, and then we
* ensure that unless pool is stopping, this thread does not have
* its interrupt set.
*
* 3. Each task run is preceded by a call to beforeExecute, which
* might throw an exception, in which case we cause thread to die
* (breaking loop with completedAbruptly true) without processing
* the task.
*
* 4. Assuming beforeExecute completes normally, we run the task,
* gathering any of its thrown exceptions to send to afterExecute.
* We separately handle RuntimeException, Error (both of which the
* specs guarantee that we trap) and arbitrary Throwables.
* Because we cannot rethrow Throwables within Runnable.run, we
* wrap them within Errors on the way out (to the thread's
* UncaughtExceptionHandler). Any thrown exception also
* conservatively causes thread to die.
*
* 5. After task.run completes, we call afterExecute, which may
* also throw an exception, which will also cause thread to
* die. According to JLS Sec 14.20, this exception is the one that
* will be in effect even if task.run throws.
*
* The net effect of the exception mechanics is that afterExecute
* and the thread's UncaughtExceptionHandler have as accurate
* information as we can provide about any problems encountered by
* user code.
*
* @param w the 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 {
//首先获取任务,为空则跳出循环
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);//执行任务前可以执行其它的业务,
//可以看到如果此方法有异常抛出则不会执行此任务的内容,使用时需要注意。
Throwable thrown = null;
try {
task.run();//用户传入的任务虽然是一个Runnable但这里只会调用它的run方法
//并不会启动用户传入的线程,转而使用worker的线程来处理
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//执行任务完成后需要执行的其它业务,可以使用if(thrown==null)来判断是否有异常出现,
//可以补救此次任务。
afterExecute(task, thrown);
}
} finally {
task = null;
//worker的任务重置为null,此工作线程执行的任务总数加1
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//是否需要回收此工作线程
processWorkerExit(w, completedAbruptly);
}
}
下面我们来看看getTask是如何实现的
/**
* Performs blocking or timed wait for a task, depending on
* current configuration settings, or returns null if this worker
* must exit because of any of:
* 1. There are more than maximumPoolSize workers (due to
* a call to setMaximumPoolSize).
* 2. The pool is stopped.
* 3. The pool is shutdown and the queue is empty.
* 4. This worker timed out waiting for a task, and timed-out
* workers are subject to termination (that is,
* {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
* both before and after the timed wait, and if the queue is
* non-empty, this worker is not the last thread in the pool.
*
* @return task, or null if the worker must exit, in which case
* workerCount is decremented
*/
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/**重点,判断是否允许核心线程超时或者工作线程数多于核心线程数。
如果是则使用超时获取任务(超时后即返回null上面的runWorker的循环就会跳出,从而回收此worker),
否则使用阻塞获取(直到有任务加入,这也是为什么在execute方法中直接加入到队列的原因。
此时并不会跳出循环,此worker也就是存活的可以处理其它任务,从而达到不用重复创建、销毁线程的目的)
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
这里只列出主要的方法,其它的方法都是一些辅助功能,如果需要了解请自行查看源码。我们以newFixedThreadPool为例看一下它是如何使用我们需要的功能的
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
对比参数说明,可以看到所谓的固定数量的线程数量就是核心线程数量与最大线程数量相等,并且不使用超时。
总结
看完上面所述是否觉得眼前一亮,又重新对线程池有了进一步的了解呢。这就是分析源码的益处,我们可以窥探作者思考路径,解决问题的思路。如果想达到线程的重用那就把任务重新包装成一个线程只要保持封装的这个线程正常运行,就可以继续处理任务,可以达到重用的目的。这个也为我们开始相似的业务提供一种实现思路。