概述:
数据库链接池: 每次与数据链接, 创建连接对象Connection 操作完之后,进行销毁 频繁创建销毁比较耗时.创建一个池子,预先在池子中初始化好一部分连接(Connection )对象, 使用时直接获取即可,用完还回,不需要频繁创建销毁.
线程池和数据库连接池的思想是一样的:
以前我们需要使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁, 而是可以继续执行其他的任务?
Java 中可以通过线程池来达到这样的效果。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
为什么使用线程池?
并发量大的情况下,频发创建销毁线程开销较大.创建线程池缓解压力.
jdk5之后,提供ThreadPoolExecutor类来实现线程池创建, 是建议被使用的(见《阿里巴巴 java 开发规范》), 里面有7个参数来设置对线程池的特征的定义.
ThreadPoolExecutor 类:
java.uitl.concurrent.ThreadPoolExecutor 类是线程池中最核心的一个类.
ThreadPoolExecutor 继承了 AbstractExecutorService 类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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.corePoolSize:
核心线程池数量, 在创建后核心线程池数量默认为0, 有任务来了后,才会去创建线程去执行, 或者调用prestartAllCoreThreads()或者 prestartCoreThread()方法,进行预创建.
2.maximumPoolSize:
线程池总数量,表示线程池最大能装多少个线程.
3.keepAliveTime:
指的是非核心线程池中的线程,在没有任务执行时空闲多长时间后销毁。
4.unit:
参数 keepAliveTime 的时间单位,有 7 种取值,在 TimeUnit 类中有 7 种静态属性:
5.workQueue:
一个阻塞队列,用来存储等待执行的任务,可以自己来指定等待队列的实现类。这个参数的选择也很重要,会对线程池的运行过程产生重大影响。
6.threadFactory:
线程工厂,主要用来创建线程;
7.handler:
表示当拒绝处理任务时的策略.
线程池的执行:
创建完成 ThreadPoolExecutor 之后,当向线程池提交任务时,通常使用 execute 方法。 execute 方法的执行流程图如下:
1.如果线程池中存活的核心线程数小于线程数 corePoolSize 时,线程池会创建一个核心线程去处理提交的任务。
2.如果线程池核心线程数已满,即线程数已经等于 corePoolSize,一个新提交的任务,会被放进任务队列 workQueue 排队等待执行。
3.当线程池里面存活的线程数已经等于 corePoolSize 了,并且任务队列 workQueue 也满,判断线程数是否达到 maximumPoolSize,即最大线程 数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
4.如果当前的线程数达到了 maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
线程池中的队列:
线程池有以下工作队列:
ArrayBlockingQueue:有界队列,是一个用数组实现的有界阻塞队列,按 FIFO 排序量。
LinkedBlockingQueue:可设置容量队列,基于链表结构的阻塞队列,按 FIFO 排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列, 最大长度为 Integer.MAX_VALUE,吞吐量通常要高于 ArrayBlockingQuene;
线程池的拒绝策略:
构造方法的中最后的参数 RejectedExecutionHandler 用于指定线程池的拒绝策略。当请求任务不断的过来,而系统此时又处理不过来的时候,我们就需要采取对应的策略是拒绝服务。
默认有四种类型:
AbortPolicy 策略:该策略会直接抛出异常,阻止系统正常工作。
CallerRunsPolicy 策略:只要线程池未关闭,该策略在调用者线程中运行当前的任务(如果任务被拒绝了,则由提交任务的线程(例如:main)直接执行此任务)。
DiscardOleddestPolicy 策略:该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
DiscardPolicy 策略:该策略丢弃无法处理的任务,不予任何处理。
execute 与 submit 的区别:
执行任务除了可以使用 execute 方法还可以使用 submit 方法。
它们的主要区别是:
execute 适用于不需要关注返回值的场景,submit 方法适用于需要关注返回值的场景。
线程池的关闭 :
关闭线程池可以调用 shutdownNow 和 shutdown 两个方法来实现。
shutdownNow:对正在执行的任务全部发出 interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。
shutdown:当我们调用 shutdown 后,线程池将不再接受新的任务,但也不会 去强制终止已经提交或者正在执行中的任务。