线程池与ThreadPoolExecutor学习笔记
来源:互联网 发布:普奇神父知乎 编辑:程序博客网 时间:2024/06/05 04:36
学习和使用Java的时间也不算短了,一直想找个机会好好学习线程池相关的技术细节,最近正好比较闲,因此抽空来把JDK 1.7线程池的实现给研究了一下。
线程池技术,不管是对于服务器端开发还是客户端开发都很重要。线程池最大的好处在于减少对象的创建和销毁带来的资源消耗。在面向对象程序编程中,创建和销毁对象是很消耗资源的。对于Java来说,更是如此,Java虚拟机会跟踪每一个对象,以便在对象被销毁后进行垃圾回收,这就是一些“池化技术”产生的原因。服务器端开发我不太熟悉,对于客户端开发来说。经常需要在非UI线程处理一些工作,如果每次都创建一个线程的话,对于CPU、内存、电池都受限制的手机来说,这显然是不可接受的。因此我们在进行Android开发时也采用线程池的技术来避免资源的消耗。由于我是Android程序猿,使用的语言是Java,因此我就使用JDK的线程池实现来讲解。
想要学习了解一个类,首先要从它的构造函数开始学习。首先我们来看一下它的一个常见的构造函数。
/*** @param corePoolSize 线程池中维护的目标线程个数,在运行一段时间以后线程个数与corePoolSize相等* @param maximumPoolSize 线程池中中维护的最大线程个数* @param keepAliveTime 当线程池中的线程个数大于corePoolSize,并且由有处于idle状态的线程时,最大的等待下一个任务的时间* @param unit keepAliveTime的时间单元* @param workQueue 保存要运行的Runnable对象的的队列* @param threadFactory 当executor需要创建新的线程时使用的factory*/public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
由构造函数可知,ThreadPoolExecutor可以创建一个线程池不断处理提交过来的Runnable对象,线程池在运行一段时间以后线程的个数等于corePoolSize,maximumPoolSize是线程池中最大的线程个数,当线程池中的线程个数大于corePoolSize并且由处于idle状态的线程,经过keepAliveTime之后线程会被销毁。线程池使用BlockingQueue来保存提交执行Runnable,当queue为空时,线程持续等待并被阻塞住。
接下来说一下ThreadPoolExecutor这个类中贯穿始终的一些变量和方法,这些正是ThreadPoolExecutor类设计的巧妙之处。ctl是一个atomic integer,它负责保存线程池的运行状态以及当前线程池的worker线程个数。整个线程池的最大容量是2^29 - 1,也就说ctl的低29位用来存储worker线程的个数,ctl的高三位,用来存储线程池的状态,如RUNNING、SHUTDOWN、STOP等等。源码中关于这么设计的解释是2^29 - 1也已经是一个非常大的数了,以后再扩展的话再换成atomic long也很方便,而且由于在ThreadPoolExecutor中需要频繁查询线程池状态以及worker线程个数,而移位运算效率又是相对比较高的,因此采用这样的方式来设计。废话少说,下面直接上源码。
//负责保存线程池状态以及worker线程数量 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; //worker线程数量最大值,2^29 - 1 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits //线程池的各种运行状态 private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl //取出ctl的高3位,获得线程池状态 private static int runStateOf(int c) { return c & ~CAPACITY; } //取出ctl的低29位,获得worker线程个数 private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; }
接下来是ThreadPoolExecutor的一些重要方法。
public void execute(Runnable command) { //首先判空,空则抛异常 if (command == null) throw new NullPointerException(); int c = ctl.get(); //如果worker线程个数小于corePoolSize if (workerCountOf(c) < corePoolSize) { //调用addWorker方法,此方法稍后解释 //大致就是新增一个线程,并把command作为此线程的第一个任务 //如果新增并且执行成功,直接返回 if (addWorker(command, true)) return; c = ctl.get(); } //如果worker线程个数 >= corePoolSize //判断线程池运行状态,并且将command添加到保存任务的Queue中 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); //再次检查线程池状态,如果挂了,则从Queue中移除command,并且关闭线程池 if (! isRunning(recheck) && remove(command)) reject(command); //否则线程池正常运行的话,如果worker线程的个数等于0,那么则新增线程 //这也就是我工作的项目中使用的线程池corePoolSize和maximumPoolSize相同的原因 //因为线程个数到达corePoolSize之后新增线程的条件很严苛,绝大多数情况下maximumPoolSize是无用的 else if (workerCountOf(recheck) == 0) addWorker(null, false); } //当线程池的状态不是running //或者虽然处于running,但是队列已经满了,拒绝添加新任务 else if (!addWorker(command, false)) reject(command); }
对于execute方法的总结,(1)当线程池接收一个新的任务时,如果当前线程个数小于corePoolSize,那么直接调用addWorker方法新增线程,并且将command作为第一个任务进行执行。(2)如果当前线程个数大于等于corePoolSize,首先判断线程池运行状态,如果状态正常,然后试图将任务添加到队列中。如果线程池状态异常或者缓存队列已经满了,尝试新增worker线程,如果新增worker线程仍失败,则拒绝command。(3)线程个数大于等于corePoolSize,线程池状态正常,并且将任务成功添加到缓存队列中,那么再次检查线程池状态。如果线程池挂了,那么拒绝任务。否则再次检查状态仍正常的话,如果worker线程个数为0,那么新增worker线程。
/** * @param firstTask 新增线程执行的第一个runnable对象 * @paran core 是否以corePoolSize为线程个数限制 */ private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); //检查线程池状态 if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) //状态异常则直接返回 return false; for (;;) { //检查worker线程数量 int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) //状态异常则直接返回 return false; //增加worker线程数量 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线程set添加对象 workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } //如果添加worker成功,执行runnable if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
addWorker方法总结:此方法主要是完成添加worker对象的工作,添加成功之后则执行 runnable,不过 runnable有可能为空。
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在addWorker 被创建之后,run方法直接被调用,而run方法又会调用至此方法。在此方法中,通过while 循环,不断调用getTask从blockingQueue中取出任务来执行,如果缓存队列为空,则线程被阻塞,直到有新的任务被放入缓存队列中 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(); } 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); } }
runWorker()的方法的核心在于通过while 循环,不断调用getTask从blockingQueue中取出任务来执行,如果缓存队列为空,则线程被阻塞,直到有新的任务被放入缓存队列中。
至此,线程池的简单学习笔记已经到此结束了,线程池对于移动端开发来说有着非常重要的意义,必须要掌握好!
- 线程池与ThreadPoolExecutor学习笔记
- 线程学习三:线程池ThreadPoolExecutor 与 Executors
- Java线程池ThreadPoolExecutor笔记
- java ThreadPoolExecutor线程池学习
- java线程API学习 线程池ThreadPoolExecutor
- java线程API学习 线程池ThreadPoolExecutor
- java线程API学习 线程池ThreadPoolExecutor
- ThreadPoolExecutor线程池的使用与理解
- 线程池ThreadPoolExecutor与阻塞队列BlockingQueue
- java 线程池ThreadPoolExecutor简介与实例
- ThreadPoolExecutor线程池的使用与理解
- Java 线程池学习--线程池ThreadPoolExecutor使用简介
- Java 线程池ThreadPoolExecutor
- 线程池 ThreadPoolExecutor
- Java 线程池ThreadPoolExecutor
- 线程池ThreadPoolExecutor
- 线程池ThreadPoolExecutor参数设置
- Java 线程池ThreadPoolExecutor
- 转载 httpclient 上传、下载文件
- Android的LinearLayout.getLayoutParams().width 和 LinearLayout.getWidth() 的区别
- 静态方法的线程安全
- HDU-5280
- 详解 Too many open files
- 线程池与ThreadPoolExecutor学习笔记
- shell环境变量以及set,env,export的区别
- Mommy, I found it!, 15 Awesome Linux Find Command Examples
- Xcode更新后,第三方插件不能用的问题解决方法
- 杭电ACM1045——Fire Net~~深度优先搜索
- Matlab获取Yahoo Finance 免费数据
- ALERT日志中常见监听相关报错之二:ORA-3136错误的排查
- WebSocket使用中碰到的问题以及解决方法
- hdu 1009 FatMouse' Trade