强烈推荐一个大神的人工智能的教程:http://www.captainai.net/zhanghan
什么是线程池
为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用,线程池是来管理线程的,线程池中总有那么几个活跃线程,当使用线程时可以从池子中随便拿一个空闲线程,当完成工作时,不用立即关闭线程,而是将线程还给线程池,方便下一个要使用线程的任务,有了线程池后,创建线程变成是从线程池中拿线程,销毁线程时变成是将线程还给线程池继续管理。 这种场景非常像在开发中连接数据库,有一个数据库连接池,需要创建连接时是从连接池中拿一个连接,释放时是将连接还给连接池
解决了什么问题
**1.多线程解决了什么问题:**之前的电脑是单核的,cpu的资源在多个线程间来回切换,现在发展的很快,电脑大多都是多核的,多线程可以考虑并行的问题,可以有效的利用cpu的资源,提高资源利用率和系统运行效率 **2.线程池解决了什么问题:**创建和销毁线程时需要消耗资源,创建出来的线程是占用内存的,线程池是来管理线程的,那么问题来了,什么时候使用线程池呢是需要使用多线程解决问题的时候就使用线程池吗答案是否定的,因为刚才说过了,创建出来的线程是占用空间的,因此线程池的使用场景是在需要大量线程的情况下,我们使用线程池管理线程,相当于把线程池的创建和销毁均分给了多个任务,在未使用线程池的时候是每个任务执行时都需要去创建和销毁线程,因此,线程池在资源耗费较大的时候,可以有效的避免频繁的创建和销毁线程,从而节省资源,线程本身也会占用内存空间,如果处理不当可能会导致Out of Memory异常,因此选择正确的等待队列是必要的,具体的会在下边详细讲解。
Executor框架结构图
![本图来自网上](https://img-blog.csdn.net/20180519153225545watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poMTU3MzI2MjE2Nzk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 上图来自于网络 1.Executor是该框架结构图中的最基础的接口,其中定义了线程池execute方法 2.ExecutorService也是一个接口,继承自Executor,定义了线程池的提交及停止,及其他一些判断线程池状态的方法 3.ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务 4.ScheduledExecutorService提供定时执行功能 5.还有一个是Executors,在本图中没有写出,Executors充当一个工厂角色,通过它可以取得一个拥有特定功能的线程池,目前提供的有四种线程池
核心线程池的内部实现
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
以上三种线程池的实现都是对ThreadPoolExecutor进行了封装,ThreadPoolExecutor这个类为什么如此的功能强大它的最重要的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
各参数说明:
1.corePoolSize:核心线程数量,指定了线程池中的线程数量,当使用Executors创建了一种线程池时,线程池中是没有活跃的线程的,当提交一个任务时,如果当前线程池中活跃线程数量小于核心线程数量,则会创建一个线程,这个线程就是活跃线程,当线程池中的线程数量达到核心线程数量,再来一个任务需要线程时,这个任务会被放到队列中,等待线程空闲
2.maximumPoolSize:指定了线程池中最大允许的线程数量,当任务来了,队列中已经满了,此时如果线程池中的线程数量没有超过maximumPoolSize的值,则继续创建线程执行任务
3.keepAliveTime:当线程池中的线程数量超过corePoolSize时,其他被创建出来的线程(不是核心线程的线程)的空闲时的存活时间
4.unit:keepAliveTime的单位,包括:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5.workQueue:任务队列,被提交单尚未被执行的任务,队列类型如下:
-
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。该类型的构造函数必须带一个容量参数,表示该队列的最大容量。
-
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。提交的任务不会被真是的保存,而总是将新任务提交给线程执行。
-
LinkedBlockingQueue,无解队列,使用时,除非系统资源耗尽,否则,任务是不存在入队失败的情况,
-
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。总能确保优先级高的任务优先执行
无界队列和有界队列各有优缺点,无界队列在任务无限多时,无界队列膨胀,耗尽资源,有界队列,可能会造成部分任务被拒绝
6.threadFactory:线程工厂,用于创建线程
7.handler:拒绝策略,当任务太多,队列满了,线程池中的线程数量达到了最大的线程数量后,就需要执行拒绝策略,JDK提供了四种拒绝策略,如下: -
AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作
-
CallerRunsPolicy策略:直接在execute方法的调用线程中运行被拒绝的任务,如果执行程序已经关闭,则会丢弃该任务
-
DiscardOledestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被调度执行的任务,并尝试再次提交当前任务
-
DiscardPolicy策略:直接丢弃任务,不予任何处理
任务执行过程
由以上的ThreadPoolExecutor的参数详解了解了各参数都是干什么的,这几个参数是如何协同工作,完成任务执行呢 **源码解析:**
public void execute(Runnable command) {
// 命令为null,抛出异常
if (command == null)
throw new NullPointerException();
/*
* 进行下面三步
*
* 1. 如果运行的线程小于corePoolSize,则尝试使用用户定义的Runnalbe对象创建一个新的线程
* 调用addWorker函数会原子性的检查runState和workCount,通过返回false来防止在不应
* 该添加线程时添加了线程
* 2. 如果一个任务能够成功入队列,在添加一个线城时仍需要进行双重检查(因为在前一次检查后
* 该线程死亡了),或者当进入到此方法时,线程池已经shutdown了,所以需要再次检查状态,
* 若有必要,当停止时还需要回滚入队列操作,或者当线程池没有线程时需要创建一个新线程
* 3. 如果无法入队列,那么需要增加一个新线程,如果此操作失败,那么就意味着线程池已经shut
* down或者已经饱和了,所以拒绝任务
*/
int c = ctl.get();// 获取线程池控制状态
if (workerCountOf(c) < corePoolSize) {// worker数量小于corePoolSize
if (addWorker(command, true)) // 添加worker
return;// 成功则返回
c = ctl.get();// 不成功则再次获取线程池控制状态
}
if (isRunning(c) && workQueue.offer(command)) {// 线程池处于RUNNING状态,将命令(用户自定义的Runnable对象)添加进workQueue队列
// 再次检查,获取线程池控制状态
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) // 线程池不处于RUNNING状态,将命令从workQueue队列中移除
reject(command); // 拒绝执行命令
else if (workerCountOf(recheck) == 0) // worker数量等于0
addWorker(null, false);// 添加worker
}
else if (!addWorker(command, false))// 添加worker失败
reject(command);// 拒绝执行命令
}
4种线程池
**1.newFixedThreadPool()固定大小的线程池** ``` public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } ```
该方法返回一个固定大小的线程池,看这种类型的线程池强两个参数corePoolSize和maximumPoolSize都是nThreads,因此它是固定大小的线程池,刚创建时,线程池中是没有活跃线程的,当创建的线程数量达到corePoolSize后,线程池中的线程数量是一直不变的
2.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
该方法返回一个只有一个线程的线程池,若多余一个任务被提交到该线程池,任务会被保存在一个队列中,待线程空闲,按先进后出的顺序执行队列中的任务
3.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
该方法返回一个可根据实际情况调整线程数量的线程池,线程池的线程数量不确定,若空闲线程可复用,则会优先使用可复用的线程
4.newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
该方法返回一个ScheduledExecutorService对象,线程池大小为1
推荐博客
- Excutor框架:https://www.cnblogs.com/study-everyday/archive/2017/04/20/6737428.html
- 四种拒绝策略解析:https://blog.csdn.net/pozmckaoddb/article/details/51478017
- 多线程线程池:https://blog.csdn.net/why15732625998/article/details/80046147