线程池内部实现
1、常用线程池定义
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
2、ThreadPoolExecutor构造方法详解
由以上代码可以看到,常用的几个方法虽然有着完全不同的功能特点,但内部实现都是ThreadPoolExecutor类的封装,下面是ThreadPoolExecutor类的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中:
corePoolSize
maximumPoolSize
keepAliveTime
unit
workQueue
threadFactory
handler
2.1 workQueue
指被提交但未执行的任务队列;是java.util.concurrent.BlockingQueue接口对象,仅用于存放Runnable对象;分类如下:
SynchronousQueue没有容量,每一个插入操作都要等待一个相应的删除操作,反之,每一个删除操作都要等待对应的插入操作;
提交的任务总是被提交给线程执行,如果没有空闲的线程则尝试新建线程,如果已经达到线程最大值则执行拒绝策略;
使用SynchronousQueue队列,通常设置很大的maximumPoolSize值,否则很容易执行拒绝策略;
如newCachedThreadPool()方法中:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
构造方法中需传入队列最大容量值
public ArrayBlockingQueue(int capacity)
当有新任务需要执行时:
与有界队列相比,除非系统资源耗尽,否则无界任务队列不存在任务入队失败的情况;
当有新任务需要执行时:
如newFixedThreadPool()方法中:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
如newSingleThreadExecutor()方法中:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
可以根据任务自身的优先级顺序控制执行先后顺序;
attention:使用自定义线程池时,要根据具体情况选择合适的并发队列作为任务的缓冲;
3、ThreadPoolExecutor.execute()方法
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
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();
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);
}
先判断当前线程数是否小于corePoolSize核心线程数(workerCountOf(c) < corePoolSize);
如果小于,则通过addWorker(command, true)直接调度执行;
如果不小于,加入等待队列(workQueue.offer(command));
如果加入等待队列失败(如有界队列到达上限,或者使用了SynchronousQueue类等),则将任务直接提交给线程池执行else if (!addWorker(command, false));
如果提交失败,执行拒绝策略reject(command);
4、拒绝策略
是系统超负荷运行时的补救措施;
JDK内置拒绝策略,都实现了java.util.concurrent.RejectedExecutionHandler接口:
CallerRunsPolicy:只要线程池未关闭,该策略会直接在调用者线程中,运行当前被丢弃的任务;
DiscardOldestPolicy:丢弃未执行任务中最老的任务,就是即将被执行的一个任务,并尝试再次提交当前任务;
DiscardPolicy:默默丢弃无法处理的任务,不做任何处理;
如果内置策略无法满足实际应用的需求,可以自己扩展RejectedExecutionHandler接口:
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
示例:核心线程、最大线程数都是5,有界任务队列容量为10,所以最多可以容纳15个线程,多余的走拒绝策略;
public class RejectExecutionHandlerDemo {
public static class TaskDemo implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
TaskDemo task = new TaskDemo();
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString() + " is discard.");
}
});
for (int i = 0; i < 16; i++) {
es.submit(task);
TimeUnit.MILLISECONDS.sleep(10);
}
es.shutdown();
}
}
console
1550415828931 : pool-1-thread-1
1550415828943 : pool-1-thread-2
1550415828954 : pool-1-thread-3
1550415828964 : pool-1-thread-4
1550415828975 : pool-1-thread-5
java.util.concurrent.FutureTask@7d4991ad is discard.
1550415829933 : pool-1-thread-1
1550415829943 : pool-1-thread-2
1550415829955 : pool-1-thread-3
1550415829969 : pool-1-thread-4
1550415829978 : pool-1-thread-5
1550415830936 : pool-1-thread-1
1550415830944 : pool-1-thread-2
1550415830959 : pool-1-thread-3
1550415830974 : pool-1-thread-4
1550415830979 : pool-1-thread-5
5、自定义线程工厂 ThreadFactory
java.util.concurrent.ThreadFactory接口用于创建线程;
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
示例:自定义线程工厂,设置所有线程为守护线程,当main线程退出后,强制注销该线程池;
public class ThreadFactoryDemo {
public static class TaskDemo implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TaskDemo task = new TaskDemo();
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
System.out.println("create " + t);
return t;
}
});
for (int i = 0; i < 5; i++) {
es.submit(task);
}
}
}
console
create Thread[Thread-0,5,main]
create Thread[Thread-1,5,main]
create Thread[Thread-2,5,main]
1550416438149 : Thread-1
1550416438149 : Thread-0
create Thread[Thread-3,5,main]
1550416438150 : Thread-2
create Thread[Thread-4,5,main]
1550416438150 : Thread-3
1550416438150 : Thread-4
6、线程池扩展
可以覆写ThreadPoolExecutor中的以下三个方法,来对线程池进行控制:
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
public class ExtThreadPool {
public static class TaskDemo implements Runnable {
private String name;
public TaskDemo(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " - " + this.name + " - is running.");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println(t.getName() + " - " + ((TaskDemo) r).name + " - beforeExecute");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println(Thread.currentThread().getName() + " - " + ((TaskDemo) r).name + " - afterExecute");
}
@Override
protected void terminated() {
System.out.println("ThreadPool is terminated.");
}
};
for (int i = 0; i < 5; i++) {
TaskDemo task = new TaskDemo("TASK-" + i);
es.execute(task);
TimeUnit.MILLISECONDS.sleep(10);
}
es.shutdown();
}
}
console
pool-1-thread-1 - TASK-0 - beforeExecute
pool-1-thread-1 - TASK-0 - is running.
pool-1-thread-2 - TASK-1 - beforeExecute
pool-1-thread-2 - TASK-1 - is running.
pool-1-thread-3 - TASK-2 - beforeExecute
pool-1-thread-3 - TASK-2 - is running.
pool-1-thread-4 - TASK-3 - beforeExecute
pool-1-thread-4 - TASK-3 - is running.
pool-1-thread-5 - TASK-4 - beforeExecute
pool-1-thread-5 - TASK-4 - is running.
pool-1-thread-1 - TASK-0 - afterExecute
pool-1-thread-2 - TASK-1 - afterExecute
pool-1-thread-3 - TASK-2 - afterExecute
pool-1-thread-4 - TASK-3 - afterExecute
pool-1-thread-5 - TASK-4 - afterExecute
ThreadPool is terminated.
7、优化线程池线程数量
Ncpu = CPU数量
Ucpu = 目标CPU使用率, 0<=Ucpu<=1
W/C = 等待时间与计算时间的比率
为保持处理器达到期望的使用率,最优的线程池大小为
Nthreads = Ncpu * Ucpu * (1 + W/C)
在Java中,通过如下代码获取可用CPU数量:
Runtime.getRuntime().availableProcessors()
8、打印线程池中的错误堆栈信息
public class TraceErrorDemo {
public static class DivTask implements Runnable {
private int a, b;
public DivTask(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
double result = a / b;
System.out.println(result);
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadPoolExecutor pools = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.MILLISECONDS,
new SynchronousQueue<>());
for (int i = 0; i < 5; i++) {
pools.submit(new DivTask(100, i));
}
}
}
console
100.0
25.0
33.0
50.0
只有4个输出,把i=0的情况漏掉了,0不能作为除数,但没有任何日志,没有错误提示,好像程序正常执行了一样;
找回部分异常信息:
1、放弃submit()方法,改用execute()方法;
pools.execute(new DivTask(100, i));
2、使用Future接收submit()方法返回值;
Future result = pools.submit(new DivTask(100, i));
result.get();
得到部分异常堆栈信息如下:
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at com.threadpool.TraceErrorDemo$DivTask.run(TraceErrorDemo.java:18)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
100.0
25.0
33.0
50.0
但是还是不知道任务是在哪里提交的,提交位置已经被线程池淹没了,所以自己动手扩展ThreadPoolExecutor线程池,在调度任务之前先保存一下提交任务线程的堆栈信息:
public class TraceThreadPoolExecutor extends ThreadPoolExecutor {
public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public void execute(Runnable command) {
super.execute(wrap(command, clientTrace(), Thread.currentThread().getName()));
}
@Override
public Future> submit(Runnable command) {
return super.submit(wrap(command, clientTrace(), Thread.currentThread().getName()));
}
private Runnable wrap(final Runnable task, final Exception clientStack, String clientThreadName) {
return new Runnable() {
@Override
public void run() {
try {
task.run();
} catch (Exception e) {
clientStack.printStackTrace();
throw e;
}
}
};
}
private Exception clientTrace() {
return new Exception("Client stack trace");
}
public static class DivTask implements Runnable {
private int a, b;
public DivTask(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
double result = a / b;
System.out.println(result);
}
}
public static void main(String[] args) {
ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.MILLISECONDS,
new SynchronousQueue<>());
for (int i = 0; i < 5; i++) {
pools.execute(new DivTask(100, i));
}
}
}
console
java.lang.Exception: Client stack trace
at com.threadpool.TraceThreadPoolExecutor.clientTrace(TraceThreadPoolExecutor.java:36)
at com.threadpool.TraceThreadPoolExecutor.execute(TraceThreadPoolExecutor.java:16)
at com.threadpool.TraceThreadPoolExecutor.main(TraceThreadPoolExecutor.java:54)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at com.threadpool.TraceThreadPoolExecutor$DivTask.run(TraceThreadPoolExecutor.java:46)
at com.threadpool.TraceThreadPoolExecutor$1.run(TraceThreadPoolExecutor.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
100.0
25.0
33.0
50.0
9、Fork/Join框架
“分而治之”的思想体现;
ForkJoinPool线程池对fork/join框架支持的接口
public ForkJoinTask submit(ForkJoinTask task)
ForkJoinTask有两个重要的子类:RecursiveAction和RecursiveTask,分别表示没有返回值和可以携带返回值的任务;
public abstract class RecursiveAction extends ForkJoinTask
public abstract class RecursiveTask extends ForkJoinTask
示例:计算数列求和
public class CountTask extends RecursiveTask {
private static final long serialVersionUID = 8000196622093620501L;
// 任务分解规模
private static final int THRESHOLD = 10000;
private long start;
private long end;
public CountTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
boolean canCompute = (end - start) < THRESHOLD;
if (canCompute) {
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
// 任务再次分解
long step = (start + end) / 100;
List subTasks = new ArrayList<>();
long pos = start;
for (int i = 0; i < 100; i++) {
long lastOne = pos + step;
if (lastOne > end) {
lastOne = end;
}
CountTask subTask = new CountTask(pos, lastOne);
pos += step + 1;
subTasks.add(subTask);
// 提交子任务
subTask.fork();
}
// 等待所有子任务结束,并将结果再次求和
for (CountTask t : subTasks) {
sum += t.join();
}
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask task = new CountTask(0, 200000L);
ForkJoinTask result = forkJoinPool.submit(task);
try {
long res = result.get();
System.out.println(res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
console
20000100000
10、Guava中对线程池的扩展
10.1 DirectExecutor
MoreExecutors中的DirectExecutor,将任务在当前线程中直接执行;
使用统一的编码风格来处理同步和异步调用,简化设计:
public class DirectExecutorDemo {
public static void main(String[] args) {
Executor exec = MoreExecutors.directExecutor();
exec.execute(() -> System.out.println("I am runnint in " + Thread.currentThread().getName() + "."));
}
}
console
I am runnint in main.
10.2 将普通线程池转为Daemon线程池
使用MoreExecutors中的如下方法:
public static ExecutorService getExitingExecutorService(ThreadPoolExecutor executor)
public class DaemonThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor exec = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
// 如果不设置,程序不会退出
MoreExecutors.getExitingExecutorService(exec);
exec.execute(() -> System.out.println("I am running in " + Thread.currentThread().getName() + "."));
}
}