总结篇(25)---Java线程及其相关(3)线程池及其问题

本文详细介绍了线程池的概念、构造函数、主要参数及其策略,并对比分析了四种常见线程池的特点,帮助读者理解如何合理选择和配置线程池。

线程池:

  • 是线程的一种使用模式。通过创建一定数量的线程,让他们时刻准备就绪等待新任务的到达,而任务执行结束之后重新回来继续待命。
  • 线程池核心的设计思想:复用线程,平摊线程的创建与销毁的开销代价

一、为什么使用线程池?


原因有一下几点:

1. 避免了线程的重复创建与销毁所带来的资源消耗。

2. 提升了任务的响应速度,任务来了直接选一个线程执行而无需等待线程的创建。

3. 线程的统一分配和管理,也方便统一的监控和调优。

二、线程池的构造函数


线程池的构造函数很多,我们挑一个参数最多的。其他构造器也差不多,无非就少几个参数,缺少的参数一般会选择默认的值。

public ThreadPoolExecutor( int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler)

三、线程池的主要参数


依据上述构造器,可以看出构造器中有几个主要参数:


(1)int corePoolSize:该线程池中核心线程数最大值
        当线程池创建新线程时,如果当前线程总数小于corePoolSize,则创建的是核心线程,如果超过corePoolSize,则创建的是非核心线程。
        在默认情况下,核心线程即使闲置也不会被销毁,会一致存活在线程池中。当然也可以让核心线程闲置一段时间后就被销毁,只需要设置allowCoreThreadTimeOut = true;


 (2)int maximumPoolSize:该线程池中线程总数最大值
        线程总数 = 核心线程数 + 非核心线程数。非核心线程:不是核心线程的线程。


 (3)long keepAliveTime:该线程池中非核心线程闲置超时时长
        一个非核心线程,如果处于闲置状态的时长超过这个参数所设定的时长,就会被销毁。
        如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。


 (4)TimeUnit unit:keepAliveTime的单位
    TimeUnit是一个枚举类型,其包括:
        1)NANOSECONDS:1微毫秒 = 1微秒/1000
        2)MICROSECONDS:1微秒 = 1毫秒/1000
        3)MILLISECONDS:1毫秒 = 1秒/1000
        4)SECONDS:秒
        5)MINUTES:分
        6)HOURS:小时
        7)DAYS:天


 (5)BlockingQueue<Runnable> workQueue:该线程池中的任务队列,维护着等待执行的Runnable对象
        当所有线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则创建非核心线程执行任务。
    常见的workQueue类型:
        1)SynchronousQueue:(这个消息队列相当于不存在一样) 这个队列结构接到任务时,会直接提交给线程处理,不会保留它,当没有空闲线程处理则创建一个线程去处理
        2)LinkedBlockingQueue:(这个线程只给核心线程处理)这个队列接收到任务时,会检查当前线程数是否小于核心线程数,如果小于核心线程数,则创建核心线程来处理任务;如果大于核心线程数,则进入等待队列中。
        3)ArrayBlockingQueue:(标注消息队列) 可以限定队列长度,接收任务的时候,如果没有达到corePoolSize的值,则新建核心线程去执行任务。如果达到了,则入队列等待。如果队列已满,则创建非核心线程去执行任务。如果线程总数超过maximumPoolSize,并且队列也满了,则发生错误。
        4)DelayQueue:(会给线程延迟的消息队列) 传进去的任务必须先实现Delay接口,这个队列接收到任务时,首先先入队,只有达到指定的延迟时间,才会执行任务。

 (6)ThreadFactory threadFactory:创建线程的方式
这是一个接口,你new的时候需要实现他的Thread newThread(Runnable)方法

例子:AsyncTask新建线程池的threadFactory参数源码
new ThreadFactory(){
    private final AtomicInteger mCount = new AtomicInteger(1);
    public Thread newThread(Runnable r){
        return new Thread(r,"AsyncTask #"+mCount.getAndIncrement());
    }
}

(7)RejectedExecutionHandler handler:线程池的拒绝策略,跑出异常专用的
        当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有一下四种拒绝策略:
        1)AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
        2)DiscardPolicy:丢弃任务,但是不抛出异常。
        3)DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
        4)CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。


        使用场景分析:
        1)AbortPolicy:如果是比较关键的业务,推荐使用此拒绝策略,这样在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
        2)DiscardPolicy:使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。(如,网站阅读量统计)
        3)DiscardOldestPolicy:此拒绝策略,是一种喜新厌旧的决绝策略,是否要采用这种拒绝策略。还得根据实际业务是否允许丢弃老任务来认真衡量。
        4)CallerRunsPolicy:此策略不会舍弃任何任务,但会造成卡顿等问题。

 四、线程池的策略


暂时未写

 五、常见的四种线程池


  • 这四种都继承ThreadPoolExecutor类,只不过传入的参数不一样而已

(1)CachedThreadPool(可缓存线程池)

        只有非核心线程,最大线程数很大,每新来一个任务,当没有空闲线程的时候就会创建一个线程。同时还有超时机制,当空闲线程超过60s没有用的话,就会被回收。
        他可以一定程度减少频繁创建/销毁线程,减少系统开销,适用于执行时间短且数量多的任务场景。
        特点:
            1)线程数量无限制
            2)有空闲线程则复用空闲线程,若无空闲线程则新建线程
            3)一定程度减少频繁创建/销毁线程,减少系统开销

(2)FixedThreadPool(定长线程池)

        一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,相应速度快。固定线程数量有系统资源设置,核心线程没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收。
        特点:
            1)可控制线程最大并发数(同时执行的线程数)
            2)超出的线程会在队列中等待
            
(3)ScheduledThreadPool(周期线程池)

        创建一个定长线程池,支持定时及周期性任务执行,通过schedule方法可以设置任务的周期执行。
        特点:
            1)支持定时及周期性任务执行


(4)SingleThreadExecutor(单线程化的线程池)

        创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO、LIFO、优先级)执行,每次任务到来后都会进入阻塞队列,然后按照顺序执行。
        特点:
            1)有且仅有一个工作线程执行任务。
            2)所有任务按照指定顺序执行,即遵循队列的入队出队规则。

六、线程池的生命周期


生命周期状态切换如下图所示:

对五种状态来做说明:

状态

运行状态描述

RUNNING

接受新任务和处理排队任务

SHUTDOWN

不接受新任务,但处理排队任务

STOP

不接受任务,不处理排队任务,中断正在进行的任务

TIDYING

所有任务都已终止,workerCount为零

TERMINATED

转换到TIDYING状态的线程将运行terminated()钩子方法

七、线程池如何知道一个线程的任务已经执行完成


7-1)使用线程池的isTerminated ()方法

        threadPool.isTerminated() 常用来判断线程池是否结束,结束了为TRUE。

        使用threadPool.isTerminated() 方法,必须在shutdown()方法关闭线程池之后才能使用,否则isTerminated()永不为TRUE,线程将一直阻塞在该判断的地方,导致程序最终崩溃。

7-2)使用submit()方法:

        在线程池中,有一个submit()方法,它提供了一个Future的返回值,我们通过Future.get()方法来获得任务的执行结果。

        当线程池中的任务没执行完之前,future.get()方法会一直阻塞,直到任务执行结束。因此,只要future.get()方法正常返回,也就意味着传入到线程池中的任务已经执行完成了!

7-3)使用 CountDownLatch

        CountDownLatch 是一个栅栏类似计数器,我们创建了一个包含 N 个任务的计数器,每个任务执行完计数器 -1,直到计数器减为 0 时,说明所有的任务都执行完了

更多java基础总结(适合于java基础学习、java面试常规题):

总结篇(1)---复用类

总结篇(2)---多态

总结篇(3)---内部类 (1)内部类的基本概念

总结篇(4)---内部类 (2)内部类之静态内部类

总结篇(5)---内部类 (3)内部类之成员内部类

总结篇(6)---内部类 (4)内部类之局部内部类

总结篇(7)---内部类 (5)内部类之匿名内部类

总结篇(8)---序列化

总结篇(9)---字符串及基本类 (1)字符串及基本类之基本数据类型

总结篇(10)---字符串及基本类 (2)字符串及基本类之java中公共方法及操作

总结篇(11)---字符串及基本类 (3)String对象

总结篇(12)---字符串及基本类 (4)Integer对象

总结篇(13)--- Java注解及元注解

总结篇(14)---JVM(java虚拟机) (1)JVM虚拟机概括

总结篇(15)---JVM(java虚拟机) (2)类加载器

总结篇(16)---JVM(java虚拟机) (3)运行时数据区

总结篇(17)---JVM(java虚拟机) (4)垃圾回收

总结篇(18)---JVM(java虚拟机) (5)垃圾回收算法

总结篇(19)---JVM(java虚拟机) (6)JVM调优

总结篇(20)---反射

总结篇(21)---Java IO

总结篇(22)---Java 进程

总结篇(23)---Java线程及其相关(1)线程介绍

总结篇(24)---Java线程及其相关(2)多线程及其问题

总结篇(25)---Java线程及其相关(3)线程池及其问题

总结篇(26)---Java线程及其相关(4)ThreadLocal

总结篇(27)---Java并发及锁(1)Synchronized

总结篇(28)---Java并发及锁(2)Volatile

总结篇(29)---Java并发及锁(3)Lock

总结篇(30)---Java并发及锁(4)常见锁及分类

总结篇(31)---JUC工具类(1)CountDownLatch

总结篇(32)---JUC工具类(2)CyclicBarrier

总结篇(33)---JUC工具类(3)Semaphore

总结篇(34)---JUC工具类(4)Exchanger

        

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sun cat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值