转自:http://www.cnblogs.com/dolphin0520/p/3932921.html
3.线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
- prestartCoreThread():初始化一个核心线程;
- prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
1
2
3
4
5
6
7
8
9
10
|
public
boolean
prestartCoreThread() {
return
addIfUnderCorePoolSize(
null
);
//注意传进去的参数是null
}
public
int
prestartAllCoreThreads() {
int
n =
0
;
while
(addIfUnderCorePoolSize(
null
))
//注意传进去的参数是null
++n;
return
n;
}
|
注意上面传进去的参数是null,根据第2小节的分析可知如果传进去的参数为null,则最后执行线程会阻塞在getTask方法中的
1
|
r = workQueue.take();
|
即等待任务队列中有任务。
4.任务缓存队列及排队策略
在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。
workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
5.任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
1
2
3
4
|
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
|
6.线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
7.线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
- setCorePoolSize:设置核心池大小
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
三.使用示例
前面我们讨论了关于线程池的实现原理,这一节我们来看一下它的具体使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public
class
Test {
public
static
void
main(String[] args) {
ThreadPoolExecutor executor =
new
ThreadPoolExecutor(
5
,
10
,
200
, TimeUnit.MILLISECONDS,
new
ArrayBlockingQueue<Runnable>(
5
));
for
(
int
i=
0
;i<
15
;i++){
MyTask myTask =
new
MyTask(i);
executor.execute(myTask);
System.out.println(
"线程池中线程数目:"
+executor.getPoolSize()+
",队列中等待执行的任务数目:"
+
executor.getQueue().size()+
",已执行玩别的任务数目:"
+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class
MyTask
implements
Runnable {
private
int
taskNum;
public
MyTask(
int
num) {
this
.taskNum = num;
}
@Override
public
void
run() {
System.out.println(
"正在执行task "
+taskNum);
try
{
Thread.currentThread().sleep(
4000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
System.out.println(
"task "
+taskNum+
"执行完毕"
);
}
}
|
执行结果:

从执行结果可以看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。
不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
1
2
3
|
Executors.newCachedThreadPool();
//创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();
//创建容量为1的缓冲池
Executors.newFixedThreadPool(
int
);
//创建固定容量大小的缓冲池
|
下面是这三个静态方法的具体实现;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
static
ExecutorService newFixedThreadPool(
int
nThreads) {
return
new
ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new
LinkedBlockingQueue<Runnable>());
}
public
static
ExecutorService newSingleThreadExecutor() {
return
new
FinalizableDelegatedExecutorService
(
new
ThreadPoolExecutor(
1
,
1
,
0L, TimeUnit.MILLISECONDS,
new
LinkedBlockingQueue<Runnable>()));
}
public
static
ExecutorService newCachedThreadPool() {
return
new
ThreadPoolExecutor(
0
, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new
SynchronousQueue<Runnable>());
}
|
从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。
四.如何合理配置线程池的大小
本节来讨论一个比较重要的话题:如何合理配置线程池大小,仅供参考。
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
如果是IO密集型任务,参考值可以设置为2*NCPU
当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
参考资料:
http://ifeve.com/java-threadpool/
http://blog.163.com/among_1985/blog/static/275005232012618849266/
http://developer.51cto.com/art/201203/321885.htm
http://blog.youkuaiyun.com/java2000_wl/article/details/22097059
http://blog.youkuaiyun.com/cutesource/article/details/6061229
http://blog.youkuaiyun.com/xieyuooo/article/details/8718741
《JDK API 1.6》