JDK1.5以前,创建线程是通过继承Thread,但是继承Thread存在如下弊端:
- 每次new Thread的时候都需要新建一个线程,性能差
- 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM
- Thread类缺少更多功能,比如更多的执行、定期执行、线程中断。
JDK1.5之后,系统自带的Executor框架,为开发者们自定义线程池带来了极大的方便。线程池的好处如下:
- 重用存在的线程,减少对象创建、消亡的开销、性能佳
- 可以有效的控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
网络异常,图片无法展示
|
下面先说说Executors。
Executors
Executors是一个线程池工厂类,里面有许多静态方法,供开发者调用。
/* 该方法返回一个固定线程数量的线程池,该线程池池中的线程数量始终不变。 * 当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。 * 若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务 * 默认等待队列长度为Integer.MAX_VALUE */ ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1); /* 该方法返回一个只有一个线程的线程池。 * 若多余一个任务被提交到线程池,任务会被保存在一个任务队列中,等待线程空闲,按先入先出顺序执行队列中的任务 * 默认等待队列长度为Integer.MAX_VALUE */ ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); /* * 该方法返回一个可根据实际情况调整线程数量的线程池。 * 线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。 * 若所有线程均在工作,又有新任务的提交,则会创建新的线程处理任务。 * 所有线程在当前任务执行完毕后,将返回线程池进行复用 */ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); /* 该方法返回一个ScheduledExecutorService对象,线程池大小为1。 * ScheduledExecutorService接口在ExecutorService接口之上扩展了在给定时间内执行某任务的功能, * 如在某个固定的延时之后执行,或者周期性执行某个任务 */ ExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor(); /* * 该方法也返回一个ScheduledExecutorService对象,但该线程池可以指定线程数量 */ ExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(1); 复制代码
以上的工具类的具体实现都是基于ThreadPoolExecutor类,处理策略都是AbortPolicy(直接抛出异常,阻止系统正常工作)
Executors 返回的线程池对象的弊端如下:
FixedThreadPool
、SingleThreadPool
: 允许的请求队列长度为Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致OOM。CachedThreadPool
、ScheduledThreadPool
、SingleThreadScheduledExecutor
: 允许的创建线程数量为Integer.MAX_VALUE
, 可能会创建大量的线程,从而导致 OOM。
通常情况下,线程池不允许使用 Executors
去创建,而是通过 ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
ThreadPoolExecutor说明
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 复制代码
参数说明:
- corePoolSize: 当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。\
- maximumPoolSize: 线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。\
- keepAliveTime: 当线程池线程数量超过corePoolSize时,多余的空余线程存活时间,即超过corePoolSize的空闲线程,在多长时间内被销毁。如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。\
- unit: keepAliveTime的单位。\
- workQueue: 任务队列,被提交但尚未被执行任务的任务。有以下几种队列模式:
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
- threadFactory: 线程工厂,用于创建线程,一般用默认的即可。也可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常有帮助。\
- handler: 拒绝策略,当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。系统内置的策略模式如下:
- AbortPolicy:直接抛出异常,阻止系统正常工作。
- CallerRunsPolicy:只要线程未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。(即如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行)
- DiscardOldestPolicy:该策略将丢弃最老的一个请求,也就是即将被执行的那个任务,并尝试再次提交当前任务。
- DiscardPolicy:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失。
- 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。
ThreadPoolExecutor 的执行流程
网络异常,图片无法展示
|
- 如果当前运行的线程少于
corePoolSize
,则创建新线程来执行任务(注意:执行这一步骤需要获取全局锁)。 - 如果运行的线程等于或多于
corePoolSize
,则将任务加入BlockingQueue
。 - 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
- 如果创建新线程将使当前运行的线程超出
maximumPoolSize
,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
方法。
ThreadPoolExecutor 样例
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20, 1,TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(15), new ThreadPoolExecutor.DiscardOldestPolicy()); 复制代码
上面的语句说明如下:
- 10是表示允许10个线程,
- 20是最多可以有20个线程在进行或等待,
- 1是线程池维护线程所允许的空闲时间为1秒,
- TimeUnt.SECONDS 线程池维护线程所允许的空闲时间的单位以秒为单位,
- 下一个参数是指线程池所使用的缓冲队列为有界队列,
- 最后一个参数是线程池对拒绝任务的处理策略抛弃旧的任务。