多线程【线程池】

1、什么是线程池

线程池:指在初始化一个多线程应用程序过程当中建立一个线程集合,而后在须要执行新的任务时重用这些线程而不是新建一个线程,java

一旦任务已经完成了,线程回到池子中并等待下一次分配任务。程序员

2、使用线程池的好处

1)控制最大并大数。缓存

2)下降资源消耗。经过重复利用已建立的线程来下降线程建立和销毁形成的消耗。服务器

3)提升响应速度。当任务到达时,任务不须要等到线程建立,而是能够直接使用线程池中的空闲线程。多线程

4)提升线程的可管理性。线程是稀缺资源,若是无限制的建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一的分配、延时执行、调优和监控等。ide

3、涉及到的类和接口

经常使用的线程池接口和类都在 java.util.concurrent包下,大体为:工具

Executor:线程池的顶级接口this

ExecutorService:线程池接口,可经过submit()方法提交任务代码.net

ExecutorService接口的实现类最经常使用的为如下两个:线程

ThreadPoolExecutor
ScheduledThreadPoolExecutor

和 Array -> Arrays、Collection -> Collections 同样,线程池的建立也是有工具类可使用的:

Executors工厂类:经过此类能够建立一个线程池

4、线程池种类

在 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

5、如何使用线程池

(一)使用步骤

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 的返回结果

6、线程池底层源码查看

newFixedThreadPool

newCachedThreadPool

newSingleThreadExecutor

newScheduledThreadPool

7、线程池7大参数

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:拒绝策略,当任务太多来不及处理,如何拒绝任务

8、线程池底层工做原理

1)线程池刚建立时,里面没有一个线程。任务队列是做为参数传进来的。不过,就算队列里面有任务,线程池也不会立刻执行它们。

2)当调用 execute() 方法添加一个任务时,线程池会作以下判断:

​ a) 若是正在运行的线程数量小于 corePoolSize,那么立刻建立线程运行这个任务;

​ b) 若是正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;

​ c) 若是这时候队列满了,并且正在运行的线程数量小于 maximumPoolSize,那么仍是要建立非核心线程马上运行这个任务

​ d) 若是队列满了,并且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会执行拒绝策略。

3)当一个线程完成任务时,它会从队列中取下一个任务来执行。

4)当一个线程无事可作,超过必定的时间(keepAliveTime)时,线程池会判断,若是当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。因此线程池的全部任务完成后,它最终会收缩到 corePoolSize 的大小。

9、线程池的4大拒绝策略

线程池中的线程已经用完了,没法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候咱们就须要拒绝策略机制合理的处理这个问题。

JDK 内置的拒绝策略以下:

1)AbortPolicy : 直接抛出 RejectedExecutionException 异常,阻止系统正常运行。

2)CallerRunsPolicy : 该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用线程。

3)DiscardOldestPolicy : 丢弃队列中等待最久的线程,而后把当前任务加入队列中尝试再次提交当前任务。

4)DiscardPolicy : 该策略默默地丢弃没法处理的任务,不予任何处理。若是容许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍没法知足实际须要,彻底能够本身扩展 RejectedExecutionHandler 接口。

10、线程池的实际使用

经过查看 Executors 提供的默认线程池的底层源码后,咱们会发现其有以下弊端:

1)FixedThreadPool 和 SingleThreadPool:

容许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而致使 OOM。

2)CachedThreadPool 和 ScheduledThreadPool:

容许的建立线程数量为 Integer.MAX_VALUE,可能会堆积大量的线程,从而致使 OOM。

而且在《阿里巴巴Java开发手册》中也有指出,线程池不容许使用 Executors 去建立,而是经过 ThreadPoolExecutor 的方式手动建立,这样的处理方式能让程序员更加明确线程池的容许规则,从而规避资源耗尽的风险。

小结:在实际开发中不会使用 Executors 建立,而是手动建立,本身指定参数。

11、线程池的手动建立

以上的参数是随手写的,实际开发中参数的设置要根据业务场景以及服务器配置来进行设置。

12、线程池配置合理线程数

设置线程池的参数时,须要从如下 2 个方面进行考虑:

系统是 CPU 密集型?

系统是 IO 密集型?

(一)CPU 密集型

CPU 密集的意思是该任务须要大量的运算,而没有阻塞,CPU一直全速运行。

那么这种状况下,应该尽量配置少的线程数量,从而减小线程之间的切换,让其充分利用时间进行计算。

通常公式为:CPU核数 + 1 个线程的线程池。

能够经过如下代码来查看服务器的核数:

Runtime.getRuntime().availableProcessors()
(二)IO 密集型

IO 密集型的意思是该任务须要大量的 IO,即大量的阻塞。

那么这种状况下会致使有大量的 CPU 算力浪费在等待上,因此须要多配置线程数。

在 IO 密集型状况下,了解到有两种配置线程数的公式:

公式一:CPU核数/(1-阻塞系数),其中阻塞系数在 0.8-0.9 之间

如:8核CPU,能够设置为 8/(1-0.9)=80 个线程

公式二:CPU核数 * 2

线程数的设置参考文章:

http://mp.weixin.qq.com/s__biz=MzI5MzYzMDAwNw==&mid=2247488456&idx=4&sn=80ee015180d46f2bd5b26c166b7dab0a&chksm=ec6e6a90db19e3867f8ea9fd5da01c3378431d6dcf940eb9820c4ab917e05851471102515d17&scene=0&xtrack=1#rd

http://mp.weixin.qq.com/s__biz=MzU1MzUyMjYzNg==&mid=2247484319&idx=1&sn=6a22ad5e324562c900a66624239cc6eb&chksm=fbf0c73ccc874e2a5d0a9c9d8e030e426104a52247b11a0069662a3d41a775f8c5332aba3981&mpshare=1&scene=24&srcid=&sharer_sharetime=1591622673427&sharer_shareid=5d06ca706f31f4d058e964b8b7ccfcc9#rd

Java新手,如有错误,欢迎指正!