这是我参与更文挑战的第16天,活动详情查看: 更文挑战
线程池介绍:
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
线程池7大参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:大于空闲时间时多余线程销毁
- unit:时间单位
- workQueue:任务队列
- threadFactory:线程工厂
- RejectedExecutionHandler:拒绝策略
如何配置合适的线程池:
我们都知道线程池有着它的核心线程数及最大线程数等等,在我们自定义线程池的时候这些都需要我们自己去做定义,那么如何来定义线程池的线程数呢,我们通常把线程池处理的类型分为如下3种:
- CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
- IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
- 混合型任务
- 可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
- 因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。
- 因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。
线程池执行方式execute()和submit():
- execute(),执行一个 任务,没有返回值。 2、submit(),提交一个线程任务,有返回值。
- submit(Callable task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。
- submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。
- submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。
- Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。
代码示例:
public class WatcherThreadPoolExecutor extends ThreadPoolExecutor {
private String watcherName;
public WatcherThreadPoolExecutor(String watcherName, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
this.watcherName = watcherName;
}
public <T> Future<T> submit(Callable<T> task) {
monitor();
return super.submit(Tracer.wrap(task));
}
public <T> Future<T> submit(Runnable task, T result) {
monitor();
return super.submit(Tracer.wrap(task), result);
}
public Future<?> submit(Runnable task) {
monitor();
return super.submit(Tracer.wrap(task));
}
private void monitor() {
log.info("{}-队列剩余:{}", watcherName, this.getQueue().remainingCapacity());
}
}
线程池状态关键字: volatile int runState;
- static final int RUNNING = 0;
- static final int SHUTDOWN = 1;
- static final int STOP = 2;
- static final int TERMINATED = 3;
- 当创建线程池后,初始时,线程池处于RUNNING状态;
- 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
- 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
- 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
execute实现原理(submit方法里面最终调用的还是execute()方法):
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
原理分析:(实质为一个单例模式)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
原理分析:
(实质为一个单例模式)
1.
(command == null)
判断提交任务是否为空,否则抛空指针
2.
poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)
判断当前线程池的核心线程数是否>=核心线程数 成立进入if语句,反之执行addIfUnderCorePoolSize(command)方法;
2.1:如果addIfUnderCorePoolSize(command)返回false则继续执行返回ture则整个方法执行完毕(方法如其名add核心线程)
3.
runState == RUNNING && workQueue.offer(command)
判断当前线程池是否处于RUNNING状态,是->放入缓存队列,
如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败,执行addIfUnderMaximumPoolSize(command)方法(方法如其名add最大线程);
4.
reject(command)
如果执行addIfUnderMaximumPoolSize方法失败,则执行reject()方法进行任务拒绝处理
5.
runState != RUNNING || poolSize == 0
这句判断是为了防止在将此任务添加进任务缓存队列的同时
其他线程突然调用shutdown或者shutdownNow方法关闭了线程池的一种应急措施。
6.
ensureQueuedTaskHandled(command)
如果是这样就执行:保证 添加到任务缓存队列中的任务得到处理。
原理如图:
任务缓存队列及排队策略:
workQueue的类型为BlockingQueue,通常可以取下面三种类型:
- ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
- LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
- synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。 任务拒绝策略:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池的关闭:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
ok!今日分享到此结束,希望可以对大家有帮助,有不对的地方希望大家可以提出来的,共同成长;
整洁成就卓越代码,细节之中只有天地