使用线程池的目的
使用线程池的目的在于减少频繁创建线程的代价和控制执行任务的线程数。
线程池线程数量设置
线程池线程数量设置依赖于要执行的任务是计算密集型还是IO密集型,如果是计算密集型,建议的线程池线程数量是CPU核心数量+1。如果是IO密集型,可以按情况增加,但是如果IO占满了, 再增加线程也是没办法提高执行速度的。
如果线程数量过多,上下文切换的代价会增加,反而影响性能。
线程池原理
线程池核心是通过一个阻塞队列来实现的
看下 ThreadPoolExecutor 的构造
//最简单情况下,我们可以通过下面代码创建一个线程池
public void foo(){
ExecutorService excutor = Executors.newFixedThreadPool(poolSize);
excutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread());
}
});
}
//为了说明线程池参数,看下线程池最全参数的构造
/**
* @param corePoolSize 线程池核心线程数,即使线程池没有要执行的任务,线程池也会持有corePoolSize数量的线程
* @param maximumPoolSize 线程池可以达到的最大线程数量
* @param keepAliveTime 如果线程池线程数>核心线程数,超过核心线程数量的线程经过keepAliveTime的时间后就会被释放
* @param unit keepAliveTime 的时间单位
* @param workQueue 用来保存要执行的任务(实现了Runnable接口)的队列
* @param threadFactory 创建线程实例的线程工厂
* @param handler 当线程池无法再接收更多任务时的处理策略
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//....
}
线程池核心方法是execute,流程如下
1、提交一个任务时,如果已经运行的线程数 < 核心线程数,直接创建一个线程执行该任务
2、提交一个任务时,如果线程数 >= 核心线程数,将任务放到阻塞队列,等待执行
3、如果队列满了,并且运行线程数 < 最大线程数,就创建临时线程执行新提交的任务,临时线程最大数量=最大线程数-核心线程数
4、如果队列满了,并且运行线程数 = 最大线程数,就调用 handler 的 rejectedExecution 方法,处理任务(默认是什么也不做)
5、临时线程创建以后,会在阻塞队列中任务全部执行完后,再等待 keepAliveTime 的时间后释放掉
线程的释放是这样的
如果线程数 <= 核心线程数,从阻塞队列中取下一个任务时使用阻塞队列的 take 方法,这样的话如果队列是空的,就会一直阻塞住,直到有新的任务加入队列, 再开始执行
如果线程数 > 核心线程数,从阻塞队列中取下一个任务时使用阻塞队列的 poll 方法, poll 只会阻塞指定的时间,如果超过这个时间还没有任务就会返回,这样线程就执行结束了,下面是JDK中的这一行代码
private Runnable getTask() {
//...
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
//...
}
其实线程池的基本原理很简单,真正复杂的地方在于并发控制,我看的也是一知半解。
下面偷个图
图片出处
http://blog.csdn.net/he90227/article/details/52576452
执行过程
public static void main(String[] args) throws InterruptedException {
final ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 5l, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("线程池已满");
}
});
for (int i = 0; i < 5; i++) {
System.out.println("task " + i);
TimeUnit.SECONDS.sleep(1);
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + "start");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread() + "end");
};
});
}
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("正在执行任务的线程数 " + executor.getActiveCount() + " 线程实例个数 " + executor.getPoolSize());
}
executor.shutdown();
}
/**
输出结果如下
task 0
task 1
Thread[pool-1-thread-1,5,main]start
task 2
task 3
Thread[pool-1-thread-2,5,main]start
线程池已满
task 4
线程池已满
Thread[pool-1-thread-1,5,main]end
Thread[pool-1-thread-1,5,main]start
正在执行任务的线程数 2 线程实例个数 2
正在执行任务的线程数 2 线程实例个数 2
Thread[pool-1-thread-2,5,main]end
正在执行任务的线程数 1 线程实例个数 2
正在执行任务的线程数 1 线程实例个数 2
Thread[pool-1-thread-1,5,main]end
正在执行任务的线程数 0 线程实例个数 2
正在执行任务的线程数 0 线程实例个数 2
正在执行任务的线程数 0 线程实例个数 2
正在执行任务的线程数 0 线程实例个数 1
正在执行任务的线程数 0 线程实例个数 1
正在执行任务的线程数 0 线程实例个数 1
**/
初始还线程池,核心线程数1,最大线程数2,队列大小1,临时线程存活时间5秒,线程池满了以后输出线程池已满
1、提交第一个任务 task0,线程池内线程数为0,启动一个线程执行task0,这个task0是第一个线程直接执行了,因此不进入队列。
2、提交第二个 task1,线程池内线程数量=核心线程数,因此task1进入队列等待执行
3、提交task2,此时线程池内线程数量=核心线程数,并且队列已经满了,因此启动临时线程,临时线程启动后直接执行task2
4、提交task3,task4,此时队列已满,线程池线程数量也达到最大=2,因此执行拒绝策略,输出线程池已满
5、线程1执行完task0,开始执行队列中的task1,临时线程也在执行task2,因此执行任务的线程数是2,线程中线程总数也是2
6、临时线程执行完task2后,等待从队列中取下一个任务(最多5秒 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)),线程1还在执行task1,因此,正在执行任务的线程数=1 线程实例个数=2
7、线程1也执行完task1了,线程1也阻塞住(workQueue.take()),等待任务队列中的下一个任务。因此 正在执行任务的线程数=0,线程实例个数=2
8、超过 keepalive 时间后,临时线程被释放掉了,核心线程依然等待阻塞队列中的任务,因此 正在执行任务的线程数=0,线程实例个数=1