线程池:指在初始化一个多线程应用程序过程当中建立一个线程集合,而后在须要执行新的任务时重用这些线程而不是新建一个线程,java
一旦任务已经完成了,线程回到池子中并等待下一次分配任务。程序员
1)控制最大并大数。缓存
2)下降资源消耗。经过重复利用已建立的线程来下降线程建立和销毁形成的消耗。服务器
3)提升响应速度。当任务到达时,任务不须要等到线程建立,而是能够直接使用线程池中的空闲线程。多线程
4)提升线程的可管理性。线程是稀缺资源,若是无限制的建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一的分配、延时执行、调优和监控等。ide
经常使用的线程池接口和类都在 java.util.concurrent包下,大体为:工具
Executor:线程池的顶级接口this
ExecutorService:线程池接口,可经过submit()方法提交任务代码.net
ExecutorService接口的实现类最经常使用的为如下两个:线程
ThreadPoolExecutor
ScheduledThreadPoolExecutor
和 Array -> Arrays、Collection -> Collections 同样,线程池的建立也是有工具类可使用的:
Executors工厂类:经过此类能够建立一个线程池
在 JDK 8 之后,一共有 5 种线程池,分别为:
固定线程数的线程池
只有一个线程的线程池
可根据任务数动态扩容线程数的线程池
可调度的线程池
具备抢占式操做的线程池
这些线程池都能由 Executors 工具类来进行建立,分别对应如下方法:
1)newFixedThreadPool:建立指定的、固定个数的线程池
2)newCachedThreadPool:建立缓存线程池(线程个数根据任务数逐渐增长,上线为 Integer.MAX_VALUE)
3)newSingleThreadExecutor:建立单个线程的线程池
4)newScheduledThreadPool:建立可调度的线程池 调度:定时、周期执行5)newWorkStealingPool:建立具备抢占式操做的线程池
对于 newWorkStealingPool 的补充:
newWorkStealingPool,这个是 JDK1.8 版本加入的一种线程池,stealing 翻译为抢断、窃取的意思,它实现的一个线程池和上面4种都不同,用的是 ForkJoinPool 类。
newWorkStealingPool 适合使用在很耗时的操做,可是 newWorkStealingPool 不是 ThreadPoolExecutor 的扩展,它是新的线程池类 ForkJoinPool 的扩展,可是都是在统一的一个 Executors 类中实现,因为可以合理的使用 CPU 进行任务操做(并行操做),因此适合使用在很耗时的任务中。
参考文章:
https://blog.csdn.net/qq_38428623/article/details/86689800
https://blog.csdn.net/tjbsl/article/details/98480843
1)建立线程池对象
2)建立线程任务
3)使用线程池对象的 submit() 或者 execute() 方法提交要执行的任务
4)使用完毕,可使用shutdown()方法关闭线程池
需求:使用线程池管理线程来简单的模拟买票程序。
public class Demo(){ public static void main(String[] args) { test(); } public static void test(){ //一、建立线程池对象 ExecutorService pool = Executors.newFixedThreadPool(4); //二、建立任务 Runnable runnable = new Runnable(){ private int tickets = 100; @Override public void run() { while (true){ if(tickets <= 0){ break; } System.out.println(Thread.currentThread().getName()+"卖了第"+tickets+"张票"); tickets--; } } }; //三、将任务提交到线程池(须要几个线程来执行就提交几回) for (int i=0; i<5; i++){ pool.submit(runnable); } //四、关闭线程池 pool.shutdown(); }
补充:
shutdown:启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务
shutdownNow:尝试中止全部正在执行的任务,中止等待任务的处理,并返回正在等待执行的任务列表。
execute() 和 submit() 的区别:
1)参数:execute 只能传递 Runnable;submit 既能够传递 Runnable,也能够传递 Callable
2)返回值:execute 没有返回值;submit 有返回值,能够获取 Callable 的返回结果
newFixedThreadPool
newCachedThreadPool
newSingleThreadExecutor
newScheduledThreadPool
ThreadPoolExecutor 的底层源码:
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; }
corePoolSize:线程池中的常驻核心线程数
maximumPoolSize:线程池中可以容纳同时指向的最大线程数,此值必须大于等于1
keepAliveTime:多余空闲线程的存活时间
(若线程池中当前线程数超过corePoolSize时,且空闲线程的空闲时间达到keepAliveTime时,多余空闲线程会被销毁,直到只剩下corePoolSize个线程为止)
TimeUnit:keepAliveTime 的时间单位
workQueue:任务队列,被提交但还没有被执行的任务
ThreadFactory:线程工厂,用于建立线程,通常用默认的便可
RejectedExecutionHandler:拒绝策略,当任务太多来不及处理,如何拒绝任务
1)线程池刚建立时,里面没有一个线程。任务队列是做为参数传进来的。不过,就算队列里面有任务,线程池也不会立刻执行它们。
2)当调用 execute() 方法添加一个任务时,线程池会作以下判断:
a) 若是正在运行的线程数量小于 corePoolSize,那么立刻建立线程运行这个任务;
b) 若是正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
c) 若是这时候队列满了,并且正在运行的线程数量小于 maximumPoolSize,那么仍是要建立非核心线程马上运行这个任务;
d) 若是队列满了,并且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会执行拒绝策略。
3)当一个线程完成任务时,它会从队列中取下一个任务来执行。
4)当一个线程无事可作,超过必定的时间(keepAliveTime)时,线程池会判断,若是当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。因此线程池的全部任务完成后,它最终会收缩到 corePoolSize 的大小。
线程池中的线程已经用完了,没法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候咱们就须要拒绝策略机制合理的处理这个问题。
JDK 内置的拒绝策略以下:
1)AbortPolicy : 直接抛出 RejectedExecutionException 异常,阻止系统正常运行。
2)CallerRunsPolicy : 该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用线程。
3)DiscardOldestPolicy : 丢弃队列中等待最久的线程,而后把当前任务加入队列中尝试再次提交当前任务。
4)DiscardPolicy : 该策略默默地丢弃没法处理的任务,不予任何处理。若是容许任务丢失,这是最好的一种方案。
以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍没法知足实际须要,彻底能够本身扩展 RejectedExecutionHandler 接口。
经过查看 Executors 提供的默认线程池的底层源码后,咱们会发现其有以下弊端:
1)FixedThreadPool 和 SingleThreadPool:
容许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而致使 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
容许的建立线程数量为 Integer.MAX_VALUE,可能会堆积大量的线程,从而致使 OOM。
而且在《阿里巴巴Java开发手册》中也有指出,线程池不容许使用 Executors 去建立,而是经过 ThreadPoolExecutor 的方式手动建立,这样的处理方式能让程序员更加明确线程池的容许规则,从而规避资源耗尽的风险。
小结:在实际开发中不会使用 Executors 建立,而是手动建立,本身指定参数。
以上的参数是随手写的,实际开发中参数的设置要根据业务场景以及服务器配置来进行设置。
设置线程池的参数时,须要从如下 2 个方面进行考虑:
系统是 CPU 密集型?
系统是 IO 密集型?
CPU 密集的意思是该任务须要大量的运算,而没有阻塞,CPU一直全速运行。
那么这种状况下,应该尽量配置少的线程数量,从而减小线程之间的切换,让其充分利用时间进行计算。
通常公式为:CPU核数 + 1 个线程的线程池。
能够经过如下代码来查看服务器的核数:
Runtime.getRuntime().availableProcessors()
IO 密集型的意思是该任务须要大量的 IO,即大量的阻塞。
那么这种状况下会致使有大量的 CPU 算力浪费在等待上,因此须要多配置线程数。
在 IO 密集型状况下,了解到有两种配置线程数的公式:
公式一:CPU核数/(1-阻塞系数),其中阻塞系数在 0.8-0.9 之间
如:8核CPU,能够设置为 8/(1-0.9)=80 个线程
公式二:CPU核数 * 2
线程数的设置参考文章:
Java新手,如有错误,欢迎指正!