线程池简介
当我们需要执行异步任务时,可以选择new一个新的线程来运行,但线程的创建和销毁是需要开销的,执行大量异步任务时这样做会对系统性能产生影响。使用线程池可以很好的解决这样的问题,线程池里面的线程是可以复用的,不用每次执行异步任务时都重新创建和销毁线程。线程池还有一个优势就是提供了一种资源限制手段和管理手段,比如可以限制线程的个数。我们可以通过一个实例来看下两者之间的差异
不使用线程池:
public static void main(String[] args) throws InterruptedException {
long t1 = System.currentTimeMillis();
AtomicLong atomicLong = new AtomicLong(0L);
Thread[] threads = new Thread[1000000];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(atomicLong.incrementAndGet());
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
long t2 = System.currentTimeMillis();
System.out.println("不使用线程池耗时:" + (t2 - t1));
}
不使用线程池耗时:67769
使用线程池:
public static void main(String[] args) throws ExecutionException, InterruptedException {
long t1 = System.currentTimeMillis();
AtomicLong atomicLong = new AtomicLong(0L);
ExecutorService executorService = Executors.newFixedThreadPool(8);
Future[] futures = new Future[1000000];
for (int i = 0; i < 1000000; i++) {
futures[i] = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return atomicLong.incrementAndGet();
}
});
}
for (Future future : futures) {
System.out.println(future.get());
}
executorService.shutdown();
long t2 = System.currentTimeMillis();
System.out.println("使用线程池耗时:" + (t2 - t1));
}
使用线程池耗时:9789
线程池主要参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
1.corePoolSize(线程池核心线程大小)
当向线程池提交任务时,如果已有线程数量少于corePoolSize时不管是否存在空闲线程,都会创建新的线程,直到已有线程数量大于或等于corePoolSize;并且线程池会维护一个最小线程数量为corePoolSize,即使这些线程处于空闲状态也不会被销毁,除非设置了allowCoreThreadTimeOut
2.maximumPoolSize(线程池最大线程数量)
线程池所允许的最大线程个数。当向线程池提交任务时,首先会去寻找空闲线程,如果有则直接执行,如果没有则会加入工作队列中,当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数
3.keepAliveTime(空闲线程存活时间)
线程池中的线程数大于核心线程数corePoolSize时,如果存在空闲线程,在指定时间(keepAliveTime)后,这个线程就会被销毁
4.unit(空闲线程存活时间单位)
keepAliveTime的时间单位
5.workQueue(工作队列)
当向线程池提交任务时,首先会去寻找空闲线程,如果没有则会加入工作队列中,任务调度时再从队列中取出任务。在ThreadPoolExecutor的构造函数中可使用以下几种BlockingQueue
ArrayBlockingQueue
基于数组的有界阻塞队列,按照FIFO排序。使用ArrayBlockingQueue时,若有新任务加入,且当前线程数大于核心线程数时则会加入工作队列。若工作队列已满,在当前线程数小于maximumPoolSize时会创建新的线程执行任务,若大于maximumPoolSize则会执行拒绝策略。
LinkedBlockingQueue
基于链表的无界阻塞队列(实际上最大容量为Interger.MAX_VALUE),按照FIFO排序。使用LinkedBlockingQueue时,若有新任务加入,且当前线程数大于核心线程数时会加入工作队列。但是,除非系统资源耗尽,否则无界队列不会存在入队失败的情况,所以对于无界队列,maximumPoolSize参数会失效。当任务处理速度和创建速度不匹配时,无界队列会快速增长,很快耗尽系统内存。
SynchronousQuene
直接提交的队列,即不缓存任务的阻塞队列。也就是说,新任务加入时不会被保存,而是直接加入调度,如果没有空闲的线程则直接创建新的线程执行任务,若线程数大于maximumPoolSize则执行拒绝策略。
PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数comparator实现
6.threadFactory(创建线程的工厂)
用于创建新线程的工厂,可以用来设定线程名、是否为daemon线程等等
7.handler(线程拒绝策略)
当线程池线程数量达到maximumPoolSize并且队列也满了,再加入线程时会执行此策略。jdk提供了以下4种拒绝策略
AbortPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
直接丢弃任务,并抛出RejectedExecutionException异常
CallerRunsPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
如果线程池没有shutdown,则直接使用调用线程执行任务,否则直接丢弃任务
DiscardPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
直接丢弃任务,什么也不做
DiscardOldestPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
如果线程池没有shutdown,则丢弃最先进入队列的任务,尝试把当前任务加入到队列中
线程池执行流程
1.线程池初始化的时候里面是没有线程的,工作队列是以参数的形式传进来的,而且就算工作队列里面有任务也不会马上去执行
2.当调用execute() 方法添加任务时会做如下判断
- 如果当前线程数小于核心线程数,则新建线程执行任务
- 如果当前线程数大于核心线程数,则将任务加入工作队列
- 如果工作队列满了,且当前线程数小于最大线程数,则新建线程执行任务
- 如果工作队列满了,且当前线程数大于最大线程数,则执行拒绝策略
3.当一个线程完成任务时,会从工作队列中取一个任务来执行
4.当一个线程空闲时,会先判断当前线程数大于是否大于核心线程数,如果当前线程数大于核心线程数,那么此线程会在存活一定时间(keepAliveTime)后销毁,最后线程池中的线程数会保持在corePoolSize的大小
线程池配置
CPU密集型
CPU密集型,就是指CPU使用率很高,如果线程池的线程数量过多,那么会导致CPU不断的切换线程,建议线程池数量设置为CPU核心数+1
I/O密集型
I/O密集型,就是CPU使用率不高,因此可以让CPU在等待I/O的时候去处理更多的任务,充分利用CPU,建议线程池数量设置为2*CPU核心数
四种线程池
线程池提供了许多可调参数和可扩张性接口,以满足不同情景的需求,让开发人员能够更方便的使用Executors的方法。
1.newFixedThreadPool
创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待
2.newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO)执行
3.newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程(线程池线程个数最多可达Integer.MAX_VALUE )
4.newScheduledThreadPool
创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行