ThreadPoolExecutor是JDK自带的并发包对于线程池的实现,从JDK1.5开始,直至我所阅读的1.6与1.7的并发包代码,从代码注释上看,均出自Doug Lea之手,从代码上看JDK1.7几乎是重写了ThreadPoolExecutor的实现代码,JDK1.6的实现比较晦涩难懂,不便于理清作者的思路,故从JDK1.7的代码说起。

ThreadPoolExecutor既然是一个线程池,那么所谓池子的概念和意义多半在于充分利用资源,线程池当然就是为了充分利用线程资源,即尽量减少新建和销毁线程所产生的开销,带着这个问题,来看下作者是如何实现这个目的的。

典型的ThreadPoolExecutor调用

     public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue);//constructor public void execute(Runnable command);

典型的使用通常为new一个ThreadPoolExecutor对象,然后调用execute方法把需要执行的代码丢进去,剩下的线程控制就交给ThreadPoolExecutor管理了。从构造函数的corePoolSize,maxnumPoolSize都好理解,但是对于keepAliveTime,我之前使用一直存在一个无解,认为是execute线程所能运行的最大时间,实际上:

/**
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
*/

当当前线程数量大于corePoolsize的时候,多出来的线程在没有任务执行的时候所能存在的最大时间。

ThreadPoolExecutor总体的代码量还是比较大,但是核心代码(对传入线程的处理,即execute方法)还是比较清晰的(JDK1.7)。整体思路如下:

1,如果线程数量小于corePoolsize则新建一个worker线程,执行传入的Runnable对象。

2,如果线程数量大于等于corePoolsize,则把当前传入的Runnable对象放到队列里面(workQueue,见构造函数)。

3,如果队列已满,则新建一个worker线程,来执行当前传入的Runnable对象。

4,如果队列已满,线程数量也达到maxnumPoolsize,则只能放弃执行传入的Runnable对象。

代码如下:

     public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}

这里存在一个问题,那就是队列workQueue的对象在什么时候被执行呢?再仔细搜索代码的执行流程,每次新建的Thread并非只是把传入的Runnable对象直接执行,而是:

         /**
* Main run loop
*/
public void run() {
try {
Runnable task = firstTask;//这里的first一般为传入的Runnable
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}

这个为worker线程的run方法,其中的getTask()就是去workQueue里面取出Runnable对象并不停的执行,只有当workerQueue为空了,这个worker线程才退出这个死循环,结束这个线程。

以上为ThreadPoolExecutor整体实现思路,但是为了保证线程安全,作者也是下了不少功夫。选取比较有趣的几个地方说下:

对于保存通过execute方法传入进来的Runnable对象,作者保存在一个BlockingQueue里面,BlockingQueue是一个interface,里面有一个特性

/*
* <p><tt>BlockingQueue</tt> methods come in four forms, with different ways
* of handling operations that cannot be satisfied immediately, but may be
* satisfied at some point in the future:
* one throws an exception, the second returns a special value (either
* <tt>null</tt> or <tt>false</tt>, depending on the operation), the third
* blocks the current thread indefinitely until the operation can succeed,
* and the fourth blocks for only a given maximum time limit before giving
* up.
*/

即是对于获取一个可能会不能马上获取的对象,这个队列必须实现4种方式:1,抛异常,2,马上返回空,3,阻塞当前调用改方法的线程,4,阻塞该线程等待一定时间,那么从队列两个主要的方法来说,insert,remove就会有8个方法。如果我们使用new CachedThreadPool(),即一个常用的生成线程池的工厂方法,workQueue将是一个SynchronousQueue对象,这个对象的特点就是:

/**
* A {@linkplain BlockingQueue blocking queue} in which each insert
* operation must wait for a corresponding remove operation by another
* thread, and vice versa. A synchronous queue does not have any
* internal capacity, not even a capacity of one. You cannot
* <tt>peek</tt> at a synchronous queue because an element is only
* present when you try to remove it; you cannot insert an element
* (using any method) unless another thread is trying to remove it;
* you cannot iterate as there is nothing to iterate. The
* <em>head</em> of the queue is the element that the first queued
* inserting thread is trying to add to the queue; if there is no such
* queued thread then no element is available for removal and
* <tt>poll()</tt> will return <tt>null</tt>. For purposes of other
* <tt>Collection</tt> methods (for example <tt>contains</tt>), a
* <tt>SynchronousQueue</tt> acts as an empty collection. This queue
* does not permit <tt>null</tt> elements.
*/

这个队列的特点就是为空,必须在有线程想从里面获取对象并已经阻塞住了,才能往里面插入对象。如果线程池使用这样的队列实现,将会有什么效果呢?还是从 从队列里面获取Runnable对象的方法来看:(该方法为公共方法,无论传入什么队列)

    Runnable getTask() {
for (;;) {
try {
int state = runState;
if (state > SHUTDOWN)
return null;
Runnable r;
if (state == SHUTDOWN) // Help drain queue
r = workQueue.poll();//2,马上返回
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);//等到一定时间,不行则放弃
else
r = workQueue.take();//3,阻塞
if (r != null)
return r;
if (workerCanExit()) {
if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers();
return null;
}
// Else retry
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}

事实上,对于传入SynchronousQueue,最终只会调用阻塞的take(),对于调用poll(),会一直返回null,因为改队列不保存对象。那new CachedThreadPool()将会返回一个这样的线程池,如果线程的数目够用,那当前存在的线程将被重用(因take()一直在那里等待新的线程),如果当前的线程们执行不过来,则会新开线程,但是这个新开的线程不会主动销毁。

作者在对操作主要对象使用了ReentrantLock这样一个锁来实现对资源访问的同步除了如下这些优点以外,我觉得在代码清晰性上也是一个大的优势:

        final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
} synchronized (lockObject) {
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask);
}

明显第一种写法在语义上看更直观一点:-D

ThreadPoolExecutor源码学习(1)-- 主要思路的更多相关文章

  1. java内置线程池ThreadPoolExecutor源码学习记录

    背景 公司业务性能优化,使用java自带的Executors.newFixedThreadPool()方法生成线程池.但是其内部定义的LinkedBlockingQueue容量是Integer.MAX ...

  2. ThreadPoolExecutor源码学习(2)-- 在thrift中的应用

    thrift作为一个从底到上除去业务逻辑代码,可以生成多种语言客户端以及服务器代码,涵盖了网络,IO,进程,线程管理的框架,着实庞大,不过它层次清晰,4层每层解决不同的问题,可以按需取用,相当方便. ...

  3. Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

    目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...

  4. Java并发包源码学习之线程池(一)ThreadPoolExecutor源码分析

    Java中使用线程池技术一般都是使用Executors这个工厂类,它提供了非常简单方法来创建各种类型的线程池: public static ExecutorService newFixedThread ...

  5. Java并发包源码学习之AQS框架(三)LockSupport和interrupt

    接着上一篇文章今天我们来介绍下LockSupport和Java中线程的中断(interrupt). 其实除了LockSupport,Java之初就有Object对象的wait和notify方法可以实现 ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  7. Java并发包源码学习之AQS框架(一)概述

    AQS其实就是java.util.concurrent.locks.AbstractQueuedSynchronizer这个类. 阅读Java的并发包源码你会发现这个类是整个java.util.con ...

  8. JDK源码学习系列01----String

                                                     JDK源码学习系列01----String 写在最前面: 这是我JDK源码学习系列的第一篇博文,我知道 ...

  9. Dubbo源码学习--集群负载均衡算法的实现

    相关文章: Dubbo源码学习文章目录 前言 Dubbo 的定位是分布式服务框架,为了避免单点压力过大,服务的提供者通常部署多台,如何从服务提供者集群中选取一个进行调用, 就依赖Dubbo的负载均衡策 ...

随机推荐

  1. 设计模式--命令模式Command(对象行为型)

    一.命令模式 将一个请求封装为一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能. (1)Command类:是一个抽象类,类中对需要执行的命令进行 ...

  2. Storm Windowing storm滑动窗口简介

    Storm Windowing 简介 Storm可同时处理窗口内的所有tuple.窗口可以从时间或数量上来划分,由如下两个因素决定: 窗口的长度,可以是时间间隔或Tuple数量: 滑动间隔(slidi ...

  3. struts 2 时间控件

    在使用struts2框架时,为我们提供了时间选择器控件:datetimepicker.但是在使用过程中会出现一些问题,主要就是struts2版本更新时做了一些修改.在struts2.0时,使用< ...

  4. 【leetcode】Largest Number

    题目简述: Given a list of non negative integers, arrange them such that they form the largest number. Fo ...

  5. [译]:Xamarin.Android平台功能——位置服务

    返回索引目录 原文链接:Location Services. 译文链接:Xamarin.Android平台功能--位置服务 本部分介绍位置服务以及与如何使用位置提供商服务 Location Servi ...

  6. notepad++快捷键

    notepad++现在是我最常用的文本编辑工具,其中使用的列模式编辑,也是很好使用的. 基本的快捷键: Ctrl-C,Ctrl-X,Ctrl-V,Ctrl-Y,Ctrl-A,Ctrl-F,Ctrl-S ...

  7. python urllib

    在伴随学习爬虫的过程中学习了解的一些基础库和方法总结扩展 1. urllib 在urllib.request module中定义下面的一些方法 urllib.request.urlopen(url,d ...

  8. 【转】OBJECT_ID和DATA_OBJECT_ID的区别

    在user_objects等视图里面有两个比较容易搞混的字段object_id和data_object_id这两个字段基本上有什么大的区别呢?object_id其实是对每个数据库中数据对象的唯一标识d ...

  9. java-并发-线程对象

    浏览以下内容前,请点击并阅读 声明 每个线程都和类Thread的实例相关,有两种基本的使用Thread对象来创建并发应用的方法: 直接控制线程的创建和管理,每次需要开始一个异步任务使简单地实例化Thr ...

  10. C++文件操作(fstream)

    C++ 通过以下几个类支持文件的输入输出: ofstream: 写操作(输出)的文件类 (由ostream引申而来) ifstream: 读操作(输入)的文件类(由istream引申而来) fstre ...