Java线程池
线程池的作用
1.重用存在的线程,减少处理多请求时线程创建、销毁产生的开销。
2.请求达到时工作线程通常已经存在,请求无需等待,提高系统的响应性。
Executors中线程池的静态工厂方法
1.newFixedThreadPool
创建一个定长的线程池,每当提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化。
2.newCachedThreadPool
创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活地回收空闲的线程,当需求增加时,它可以灵活添加新的线程。
3.newSingleThreadExecutor
创建一个单线程化的executor,它只创建唯一的工作者线程来执行任务,executor会保证任务依照任务队列所规定的顺序(FIFO、LIFO、优先级)执行。
4.newScheduledThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。
ThreadPoolExecutor类中线程池的构造函数
/** * 通过给定的初始化参数创建一个线程池(ThreadPoolExecutor) * * @param corePoolSize 线程池的核心线程数,核心线程即使空闲也不会被销毁,
* 除非allowCoreThreadTimeOut被设置 * @param maximumPoolSize 线程池中允许的最大线程数 * @param keepAliveTime 表示当前线程数大于核心线程数时,空闲线程被销毁前
* 等待新任务到达的最大时间 * @param unit keepAliveTime参数的时间单位 * @param workQueue 在任务被执行前用来存放的队列,这个队列将仅仅存放execute方法提交的Runnable任务 * @param threadFactory executor创建新线程所用的工厂 * @param handler 由于线程数量和队列容量已满,执行被阻塞时的拒绝策略 * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
线程池的参数
1.核心线程数:即使在空闲时也不会被销毁的线程数
2.最大线程数:线程池中允许的最大线程数
3.保持存活时间:空闲线程在被销毁之前等待新任务的时间
4.存活时间单位:等待时间的单位
5.工作队列:存放待执行任务的队列
6.线程工厂:新线程创建的工厂
7.拒绝策略:当线程数量和队列已满时采取的拒绝策略
工作队列
工作队列有多种,但都实现BlockQueue接口
队列类型 | 线程池类型 | 特点 |
LinkedBlockingQueue |
FixedThreadPool SingleThreadPool 使用的工作队列 |
基于链表的先进先出队列 |
SynchronousQueue | CachedThreadPool使用的工作队列 | 不保存提交的任务,而是直接创建一个线程来执行新任务 |
DelayedWorkQueue | ScheduledThreadPool使用的工作队列 |
拒绝策略
1.AbortPolicy:直接丢弃任务并抛出RejectedExecutionException异常。
2.CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
3.DiscardOldestPolicy:丢弃队列中最老的一个任务,也就是即将要执行的任务,并再次尝试提交当前任务。
4.DiscardPolicy:丢弃任务,不做任何处理。
线程池任务处理策略
线程池的任务处理如下图所示。
逻辑较为清晰,其实这是线程池execute()方法的逻辑,下面看一下源码:
//通过execute向线程池提交任务
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get();
//如果当前线程数未达到核心线程数,则直接创建线程来执行新任务 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); }
//否则将任务加入阻塞队列,这里进行双重检查,如果线程池已经关闭,则调用reject(),
//如果当前线程池线程数为0,则新建线程 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); }
//如果加入阻塞队列失败,则尝试新建一个线程,如果失败了
//则说明线程池关闭了或者线程达到最大线程数,因此调用reject() else if (!addWorker(command, false)) reject(command); }
线程池提供了两个方法,用来关闭线程池。
(1)shutdown():不会立即关闭线程池,但也不接受新的任务,等待队列中所有任务执行完毕后关闭。
(2)shutdownNow():立即终止线程池,并尝试打断正在执行的任务,清空工作队列,返回尚未执行的任务。
线程池的线程数应该如何设置
Nthreads = Ncpu * (1 + w/c) w为阻塞时间,c为计算时间
IO密集型:w/c>1,因此线程数应该为cpu的数倍,但需要考虑线程所占内存,因此通常将线程数设置为cpu的2倍。
CPU密集型:w/c=0,因此线程数为CPU个数。通常将线程数设置为CPU+1,防止线程由于偶尔出现的原因而暂停。