什么是线程池
使用线程池的原因
众所周知,线程是一种比较昂贵的资源,其主要的开销有以下几个方面
- 线程的创建与启动的开销(与普通的对象相比,Java线程会占用额外的存储空间--栈空间)
- 线程销毁的开销
- 线程调度的开销(如上下文切换的开销)
因此,对于线程这种代价比较昂贵的资源,需要使用一种合理的方式去使用,而线程池就是一种能够有效利用线程的方式。
线程池的构成
以java.util.concurrent.ThreadPoolExecutor类为例,其参数最多构造函数如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
各参数含义
- corePoolSize: 核心线程数。在初始状态下客户端每提交一个任务线程池都会创建一个工作线程,直到工作线程数达到核心线程数,之后的任务将会被存入工作队列
- maximumPoolSize: 最大线程数。 但工作线程数达到最大线程数时,客户端提交的任务会被拒绝
- long keepAliveTime,TimeUnit unit: 指定线程池中空闲线程的最大存活时间
- BlockingQueue
workQueue:工作队列。 存储客户端提交的任务,当工作队列满了以后会继续创建工作线程直到线程数达到最大线程数 - RejectedExecutionHandler handler:当客户端提交的任务被拒绝时被调用
创建线程池
- 创建一个核心线程数为5,最大线程数为10,最大存活时间为10s,工作队列容量为10,默认RejectedExecutionHandler的线程池
- 然后创建提交100个线程任务并sleep足够长的任务以确保线程在线程池中都无法执行完毕
public class ThreadPoolDemo {
public static void main(String[] args) {
ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, workQueue);
for (int i = 0; i < 100; i++) {
Task task = new Task();
threadPoolExecutor.submit(task);
System.out.println("poolSize:" + threadPoolExecutor.getPoolSize());
System.out.println("workQueue:" + threadPoolExecutor.getQueue().size());
System.out.println("largestPoolSize:" + threadPoolExecutor.getLargestPoolSize());
System.out.println("activeCount:" + threadPoolExecutor.getActiveCount());
System.out.println("taskCount:" + threadPoolExecutor.getTaskCount());
System.out.println("completedTaskCount:" + threadPoolExecutor.getCompletedTaskCount());
System.out.println("------------------------------------------");
}
ExecutorService executorService = Executors.newFixedThreadPool(1);
}
static class Task implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadName:" + Thread.currentThread().getName());
}
}
}
运行结果
poolSize:1
workQueue:0
largestPoolSize:1
activeCount:1
taskCount:1
completedTaskCount:0
------------------------------------------
...
poolSize:5
workQueue:0
largestPoolSize:5
activeCount:5
taskCount:5
completedTaskCount:0
------------------------------------------
可以看出在submit前5个任务的时候,线程池为每个任务都创建了一个线程,poolSize为5达到了核心线程数,继续submit任务
poolSize:5
workQueue:1
largestPoolSize:5
activeCount:5
taskCount:6
completedTaskCount:0
------------------------------------------
...
poolSize:5
workQueue:10
largestPoolSize:5
activeCount:5
taskCount:15
completedTaskCount:0
------------------------------------------
在达到核心线程数5后可以发现继续submit任务后线程池并不会再继续创建线程了,任务都会被放到工作队列之中,直到达到工作队列的容量上限,继续submit任务
poolSize:6
workQueue:10
largestPoolSize:6
activeCount:6
taskCount:16
completedTaskCount:0
------------------------------------------
...
poolSize:10
workQueue:10
largestPoolSize:10
activeCount:10
taskCount:20
completedTaskCount:0
------------------------------------------
当达到workQueue的上限10后,继续submit任务可以发现线程池又继续增加线程直到达到最大线程数,如果再继续添加任务那么线程线程池就会拒绝任务并调用默认的RejectedExecutionHandler向外抛出异常
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1453f44 rejected from java.util.concurrent.ThreadPoolExecutor@ad8086[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at thread.ch8.ThreadPoolDemo.main(ThreadPoolDemo.java:20)
通过这个demo就可以比较快速的了解线程池的构成与运作方式了,至于更加深入的内容在之后的博客中会继续分析。