强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan
什么是线程池 |
解决了什么问题 |
Executor框架结构图 |
核心线程池的内部实现 |
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策略:直接丢弃任务,不予任何处理
任务执行过程 |
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种线程池 |
该方法返回一个固定大小的线程池,看这种类型的线程池强两个参数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
推荐博客 |