线程池(一):基本
比较好的博文链接
1.引言-优点
使用线程池能够带来三个好处:1.降低资源消耗。通过重复利用线程池中已经创建好的的线程来降低创建和销毁线程造成的消耗。2.提高响应速度。任务到达时不需要等待线程创建就可以立即执行。3.提高线程的可管理型。大量的创建线程会消耗系统资源、降低系统稳定性,使用线程池可以统一的进行管理,即分配调优和监控。
2.线程池的创建
线程池接口及其实现类如下图:
我们可以使用Executors的方法创建四种基本的线程池,见第二篇博文介绍。创建线程池所需的几个参数含义如下(可以在ThreadPoolExecutor中参看具体代码):
-
corePoolSize:线程池中线程的数量,及时他们是空闲的;
-
maximumPoolSize:线程池中允许存在的最大线程数量,maximumPoolSize>=corePoolSize。注意线程和任务的区别;
-
workQueue( BlockingQueue
):用于保存待执行任务的阻塞队列,队列只保存将要提交给线程池 Executor的execute执行的Runnable任务; -
threadFactory:创建线程的线程工厂;
-
keepAliveTime和TimeUnit:当线程池中线程的数量大于corePoolSize时,这两个参数描述多余的线程在结束前等待新任务的时间;
-
defaultHandler(RejectedExecutionHandler):当线程池和队列都饱和后,新任务需要采取一种策略来提交,以下是线程池可使用的5种策略:
- AbortPolicy:直接抛出异常,默认处理器;
- CallerRunsPolicy:只用调用者所在线程来运行任务;
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉;
- 继承RejectedExecutionHandler接口,自定义处理方式;
3.提交任务到线程池、关闭线程池
java线程池有两种方式提交任务:execute(Runnable)和Future<T> submit(Callable<T> task),前者没有返回值所以不知道任务是否被执行成功,后者通过使用返回结果的get方法来判定任务是否执行成功。
get方法会阻塞住直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。
关闭线程池有shutdown()、shutdownNow()两种方法,区别是:
- 前者不会停止正在执行的任务,后者会尝试停止执行的任务;
调用其中一个方法,isShutdown()就会返回TRUE,当所有任务都关闭时,isTerminated就会返回TRUE。
4.线程池分析:源码、
任务提交方法源码
当一个任务提交给线程池后,发生了什么?如下源码所示:
public void execute(Runnable command) {
//如果任务对象为null,则抛异常
if (command == null)
throw new NullPointerException();
/*
* 分三步处理提交的任务:
* 1. 如果当前运行的线程数量小于核心线程数量(corePoolSize),
* 则尝试开启一个新线程并将任务作为他的第一个任务。
* 2.如果核心线程数量饱和,则进入尝试将任务加入任务队列;
* 3.如果任务队列已满,则查看当前线程数量是否已经超过线程池
* 的最大线程数量(maximumPoolSize),没有则启动新线程执行任务
* ,否则执行饱和策略(RejectedExecutionHandler)
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
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);
}
else if (!addWorker(command, false))//加入工作队列失败,执行饱和策略
reject(command);
}
...
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
...
线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会无限循环获取工作队列里的任务来执行。
5.线程池监控
- 通过继承线程池并重写线程池的
beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情; - 线程池里有一些属性在监控线程池的时候可以使用
- taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不+ getActiveCount:获取活动的线程数。
- taskCount:线程池需要执行的任务数量。
6.其他
Runtime.getRuntime().availableProcessors()获取当前设备cpu数量;- 优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行;
- 建议使用有界队列,有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点,比如几千。有一次我们组使用的后台任务线程池的队列和线程池全满了,不断的抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞住,任务积压在线程池里。如果当时我们设置成无界队列,线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。
- 依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。Http连接池同理;
浙公网安备 33010602011771号