Java线程池核心类ThreadPoolExecutor
来源:互联网 发布:淘宝婚庆四件套 编辑:程序博客网 时间:2024/06/08 00:37
通常,当需要同时处理的任务比较多时,为了避免为每个任务开一个线程(因为这样会导致频繁的线程开启和销毁,开销较大),采用线程池技术来进行线程资源的复用。
在应用中,我们通常使用Executors类提供的静态方法来使用线程池:
ExecutorService exec0 = Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUEExecutorService exec1 = Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池ExecutorService exec2 = Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池
其中ExecutorService是一个接口,实现线程池的核心是一个ThreadPoolExecutor。将任务比如一个Runnable,或者一个Callable作为参数传入submit方法即完成任务向线程池的提交,将在线程池中自动执行该任务。返回参数可以是一个Future<?>对象。
那么线程池内部具体是怎么实现的,诸如ExecutorService接口和和TheadPoolExecutor又有什么关系呢?下面通过jdk中的源码来学习一下。
与ThreadPoolExecutor类相关的接口和类的关系
public class ThreadPoolExecutor extends AbstractExecutorService{...};public abstract class AbstractExecutorService implements ExecutorService {...}public interface ExecutorService extends Executor{...}public interface Executor { void execute(Runnable command);}
可以清楚地看到它们之间的关系,Executor 接口只声明了一个方法,而ExecutorService接口在其基础上扩展出了一些方法:
public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;}
其中的submit是我们比较关注的方法,也就是核心的提交任务的方法。
AbstractExecutorService则实现了上述方法,比如如下三个版本的submit方法实现:
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; } public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask; } public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }
可以看到,提交任务的方式可以是Runnable也可以是Callable,并且为了最终可以返回参数,同意采用了将task包装成RunnableFuture,该接口双重继承了Runnable接口和Future接口。然后就是execute方法的调用,execute方法的实现交给了继承AbstractExecutorService类的子类来完成,这里就是我们要学习的核心类:ThreadPoolExecutorService。
核心类ThreadPoolExecutorService
其核心的构造器接口如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
其中corePoolSize、maximumPoolSize分别表示核心池的大小,最大线程池的大小。keepAliveTime和空闲线程的存活时间有关,workQueue是一个阻塞队列,用于缓存任务,后面两个参数分别用于产生线程和拒绝服务的处理方式。
首先,我们想搞清楚当一个任务通过线程池submit后,是怎么被执行的而submit实际上也就是调用了execute方法,所以execute就是线程池最核心的部分。
这篇博客中介绍的比较清楚了,但是我看了下我jdk版本是1.8.0_40,发现execute的实现和文章中介绍的有差异,细看了一下基本实现思路应该还是一样的。
在我的jdk版本中,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); }
先是判断当前线程池中的线程数和corePoolSize的大小,如果小于,则直接为新进的任务增加一个工作线程addWorker(command, true)
,并返回。否则尝试将任务添加到阻塞队列中,后面是检查一些异常情况,如是否外部将线程池停止isRunning(recheck)
等。最后还有拒绝服务的情况。
下面我们再来看addWorker
private boolean addWorker(Runnable firstTask, boolean core) { retry: 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 { 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(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
这个方法用于增加工作线程,所以传入的参数其中的任务叫firstTask
, 可以看到前面一部分还用到了跳转指令,retry点。for(;;){}一部分还没看太懂,大致可以看出是对非正常执行情况的处理,都是一些跳转或返回。
后面的try块是我们关注的重点,首先将firstTask包装成了一个Worker类,还获取了Worker的线程得到t,向workers这个HashSet中添加工作线程,最后调用t.start();
来启一个工作线程。
我们再来看Worker类:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{...}
再来看其核心run()方法,
public void run() { runWorker(this); }
调用了ThreadPoolExecutor的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) { w.lock(); 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(); } 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); } }
到这个方法执行时,工作线程已经启动了,进入这个方法后就不断在任务缓冲队列中提取任务执行,执行的过程中对该Worker加锁,直到任务队列为空。
注意到最后的finally语句块中的processWorkerExit(w, completedAbruptly);
,查看源代码
private void processWorkerExit(Worker w, boolean completedAbruptly) { if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; workers.remove(w); } finally { mainLock.unlock(); } tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } }
可以看到,从workers中将移出该工作线程。还可以看到allowCoreThreadTimeOut参数对行为的影响:该参数为false,则对core threads在即使空闲的情况下也不会被杀死,如果为true,则空闲的线程在一定的timeout后将被系统回收。在程序中就是若为false,则min=corePoolSize,如果当前剩余的工作线程大于等于该值,则不需要添加idle线程,否则需要增加一个idle线程,因为刚刚kill了一个工作完成的线程。
另外,还有线程池的关闭,shutdown()和shutdownNow(),前者不会立即终止线程,而是等待任务缓冲队列中的的任务被完全执行后在结束,但是期间不再接受新的任务。后者则是立即终止所有的线程,并且清空缓冲队列,返回尚未执行的任务。
应用
一般使用时,我们不是直接使用ThreadPoolExecutor类,而是通过文章开头时介绍的Executors工具类的静态方法来获得该类的实例对象。这就避免了繁杂的参数配置可能导致的潜在错误。
如何配置线程池的大小问题
一个策略就是根据任务的类型来配置线程池的大小:
对CPU密集型任务,我们采用Ncpu+1,对IO密集型任务,我们采用2*Ncpu。
- Java线程池核心类ThreadPoolExecutor
- Java线程池类ThreadPoolExecutor
- java线程池:ThreadPoolExecutor类
- Java并发编程 之 线程池核心ThreadPoolExecutor
- Java线程之线程池--类ThreadPoolExecutor
- Java 线程池ThreadPoolExecutor
- Java 线程池ThreadPoolExecutor
- Java 线程池ThreadPoolExecutor
- JAVA线程池:ThreadPoolExecutor
- JAVA线程池ThreadPoolExecutor
- JAVA线程池ThreadPoolExecutor
- ThreadPoolExecutor java 线程池
- Java 线程池ThreadPoolExecutor
- java 线程池ThreadPoolExecutor
- JAVA线程池ThreadPoolExecutor
- java线程池:ThreadPoolExecutor
- Java线程池ThreadPoolExecutor
- Java 线程池 ThreadPoolExecutor
- 例4.10 POJ3525/LA3890离海最远的点 半平面交 + 二分法 + double小数点后有效位数处理方式/printf与g++、c++的问题
- 计算机图形学(二)输出图元_10_多边形填充区_4_多边形表
- 双栈(Dual Stack)
- JAVA第三方库
- Android 学习笔记之七 Application类
- Java线程池核心类ThreadPoolExecutor
- Android内核开发:系统启动速度优化
- qt中的TabWidget应用程序框架
- 长连接的几种实现方式
- 探索推荐引擎内部的秘密,第 3 部分: 深入推荐引擎相关算法 - 聚类
- json解析
- CentOS 解压缩
- 查看关闭端口
- hdoj 3507 Print Article