文章目录
为什么要用线程池
降低资源的消耗
:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
提高响应速度
:当任务到大时,任务可以不需要等待线程创建就能立即执行
方便管理
:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控
常规实现的线程池(通过创建不同的ThreadPoolExecutor对象)
为了能够更好的控制多线程,JDK提供了一套Executor
框架,帮助开发人员有效的进行线程控制,其本质就是一个线程池
以上成员均在java.util.concurrent
包中,是JDK并发包的核心类,其中ThreadPoolExecutor
表示一个线程池,Executors
类则扮演这线程池工厂的角色,通过Execurors
可以取得一个拥有特定功能的线程池,从UML图中一刻值,ThreadPoolExecutor
类实现了Executor
接口,因此通过这个接口,任何Runnable
的对象都可以被ThreadPoolExecutor
线程池调度。
newFixedThreadPool 定长线程池
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
coreSize和maximumPoolSize都是用户设定的线程数量nthreads
keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停掉,但这里KeepALIVE无效
阻塞队列是一个无界队列,实际线程数量将永远维持着在nthreads,因此m,aximumPoolSize和keepAliveTime将无效
newCachedThreadPool可缓存
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
它是一个可以无限扩大的线程池;
它比较适合处理执行时间比较小的任务;
corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
keepAliveTime为60s,意味着线程空闲时间超过60S就会被杀死;
采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须找到一条工作线程来处理它,如果当前没有空闲的线程,那么就会再创建一条新的线程、
newSingleThreadExecutor单一线程
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
- 它指挥创建一条工作线程处理认为
JUC里面线程池内部实现
由线程池的代码可知,它们都是ThreadPoolExecutor
类的封装,为何ThreadPoolExecutor
有如此强大的功能?看看它的构造函数
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* 核心线程池大小(在线程池中的线程(最少保持这么多个))
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* 线程池中允许的最大的线程的个数
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* 超时了没有人调用就会释放线程
* @param unit the time unit for the {@code keepAliveTime} argument
* 超时单位
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* 阻塞队列,在这个队列中维持了将被执行的任务。
* @param threadFactory the factory to use when the executor
* creates a new thread
* 线程工厂:创建线程的,一般不用动
*
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* 拒绝策略当 任务被阻塞并且阻塞队列已经满了
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor
类实现了ExecutorServices
接口和Executor
接口,并由Executors
类扮演线程池工厂的角色
ThreadpOOLeXECUTOR
的七大参数
- corePoolSize:核心线程池大小(线程池维护线程的最小数量)
- maximumPoolSize:线程池维护线程最大数量
- keepAliveTime:超时了没有人调用就会释放
- unit: 线程池维护线程所允许的空闲时间单位
- workQueue: 线程池所使用的缓冲队列,任务队列,被提交但尚未被执行的任务
- handler:线程池对拒绝任务的处理策略
hanler阻塞队列
在线程池中的阻塞队列分为
直接提交队列
,有界队列
,无界队列
,优先级任务队列
直接提交队列
:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要在执行一个删除操作才会唤醒,反之每一个删除操作也都要等待对应的插入操作;`使用SynchronoousQueue队列,通常要设置很大的maximumPoolSize值,否则很容易执行拒绝策略
有界的任务队列
:有界的任务对哦咧可以使用ArrayBlockingQueue
实现。若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到CorePoolSize
时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue
初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSIZe,则执行拒绝策略
无界的任务队列
:无界任务队列可以使用LinkedBlockingQueue
实现。使用无界任务多了,线程池的任务队列可以无限制的添加新的任务,而线程池创建最大线程书刘昂就是你corePoolSize设置的数量,也就是说你设置的maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池数量达到corePoolSize后,就不会再增加了;若有后需的新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调和控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题
优先任务队列
:优先任务队列通过PriorityBlockingQueue
实现。通过运行的代码我们可以看出PriorityQuue
它其实就是一个特殊的无界队列,它其中无论添加多少个任务,线程池创建的线程数页不会超过corePoolSzie的数量,只不过其他任务队列一般是按照先进先出的规则处理任务,而PrioritYqUEWUE
队列可以自定义规则根据任务的优先级顺序先后执行
线程池拒绝策略
当任务数量超过系统实际承载能力时,该如何处理?这就需要使用到拒绝策略。拒绝策略可以说是系统超负荷运行时的补救措施,通常由于压力太大而引起的,也就是线程池中的线程已经用完了,无法继续为新任务服务,同时,等待对列中 也已经排满了,再也塞不下新任务了,这时就需要一套机制,合理的处理这个问题
- ThreadPoolExecutor.AbortPolicy():直接抛出异常,丢弃任务(当都满了)
- new ThreadPoolExecutor.CallerRunsPolicy() :不想抛弃执行任务,但是由于池中没有任何资源了,那么就会直接使用调用该
execute
的线程本身来执行,很有可能造成当前线程也被阻塞- new ThreadPoolExecutor.DiscardOldestPolicy():若程序执行尚未关闭,则位于工作队列头部的任务将被删除,然后重复执行程序(如果再次失败,则重复过程)
- ThreadPoolExecutor.DiscardPolicy():该策略默默的丢弃无法处理的任务,不予任何处理
源代码
/**
* A handler for rejected tasks that runs the rejected task
* directly in the calling thread of the {@code execute} method,
* unless the executor has been shut down, in which case the task
* is discarded.
* 哪里来的那里去
* 只要
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*在调用任务的线程中执行
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
//在调用者线程者线程中,运行当前被丢弃的任务。
//显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降
r.run();
}
}
}
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
* 直接抛出异常。
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
/**
* A handler for rejected tasks that silently discards the
* rejected task.
* 队列满了,丢掉任务,不会抛出异常,其实就是什么也不做
* 该策略陌陌的丢弃无法处理的任务,不进行任何处理
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*什么也不做和
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
/**
* A handler for rejected tasks that discards the oldest unhandled
* request and then retries {@code execute}, unless the executor
* is shut down, in which case the task is discarded.
* 该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
*
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* 将队列的首部任务直接丢弃,然后执行当前的任务
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
线程池任务调度策略
当一个任务通过execute(Ruunabe)方法与添加到线程池时
- 若此时线程池中的数量小于
CorePoolSize
,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务- 若此时线程池的数量等于
CorePoolSize
,但是缓冲队列workQueue
未满,那么任务被放入到缓冲队列- 若此时线程池中的数量大于
CorePoolSize
,缓冲队列满,并且线程池中数量小于maximumPoolSize
,创建新的线程来处理被添加的任务- 若此时线程池中的数量大于
CorePoolSize
,缓冲队列workQuieue
满,并且线程池数量等于maximumPoolSize
,那么通过handler
锁指定的策略来处理任务也就是:
处理任务的优先级别为:核心线程
CorePoolSize
,任务队lie,最大线程数;若三者都满了,使用handler处理被决绝的策略当线程池中的线程数量大于
CorePOOLsIZE
时,若某线程空闲时间超过`KEEPALIVETIME·,线程将被终止。这样,线程池就可以动态的调整池中的线程数
线程池中submit和execute有什么区别??
- execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
- execute() 没有返回值;而 submit() 有返回值
- submit() 的返回值 Future 调用get方法时,可以捕获处理异常
/**
* Executes the given command at some time in the future.
* 在将来的某个时刻执行给定的命令
* The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*这条命令可以在一条新线程或者一个线程池 中执行,取决于executor的实现
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
线程数设置为多少合适??
一般来说,确定线程池的大小需要考虑CPU数量,内存大小等因素
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程
是否使用线程池就一定比使用单线程高效呢?
答案是否定的,比如Redis就是单线程的,但它却非常高效,基本操作都能达到十万量级/s。从线程这个角度来看,部分原因在于:多线程带来线程上下文切换开销,单线程就没有这种开销。