多线程高并发高级篇(一)--线程池核心详解

说核心内容之前,需要问一个问题,既然单个线程的创建和销毁都很简单,我们为什么要使用线程池?

使用池化技术是为了什么?

估计工作过很多年的老鸟们对这些东西都能说出个一二三来,无非就是以下几点:

1、线程相对于进程而言,虽然是轻量级的,但是它的创建依然需要占用我们那点宝贵的内存资源。如果无限制的创建线程,对应垃圾回收而言也是很有压力的,毕竟线程也是对象。使用线程池统一进行线程的调度,便于管理和控制。

2、线程的创建和关闭是需要花费时间的,这点毋庸置疑吧。如果任务非常多,频繁的创建和销毁线程也是需要占用系统的分片时间的,对于系统而言,这点时间对于处理任务的时间来说,有点浪费。

总结一下,使用线程池的目的就是将系统创建的线程进行复用,节约创建和销毁线程带来的时间开销。使用线程池后,创建线程就变成了从线程池获取空闲线程(当然,没有线程的时候也是需要创建的),销毁线程就变成了将线程归还线程池。

创建线程是为了什么?当然是为了处理任务(Task)。
一、JDK对线程池的支持

JDK提供了一套Executor框架,帮助开发人员有效的进行线程控制。

Executor框架结构图(最好自己画个UML类图,首先需要知道类图中每个符号的意思,这是源码必备的分析方法):
uml介绍:https://www.cnblogs.com/me115/p/4092632.html
依赖关系是用一套带箭头的虚线表示的;如下图表示A依赖于B;他描述一个对象在运行期间会用到另一个对象的关系;它是一种临时性的关系,通常在运行期间产生,并且随着运行时的变化; 依赖关系也可能发生变化;
实现关系用一条带空心箭头的虚线表示;
在这里插入图片描述
以上涉及到的核心成员类都在java.util.concurrent包下。

上图是全部的UML类图关系,猛然一看,估计你不懵都难。所以,我们抛去依赖关系,得到最简图,这样理解起来就不费劲了。
在这里插入图片描述
那么,我们说的线程池是谁?就是ThreadPoolExecutor。我学习东西喜欢刨根问底,所以,我想问为什么它就是线程池?我们看上图,所有接口的第一个实现类是谁(不算抽象类)?对头,就是ThreadPoolExecutor。ScheduledThreadPoolExecutor扩展了线程池的功能(为什么英文起名字要见名知意,你从Scheduled是不是明白了什么),可以把它作为扩展功能了解。

以上整个代码架构,有人称之为Executor框架(不要一听框架就觉得有多么神奇高大,它就是个名字,因为线程池的一整套代码组织的起源是Executor,往上看下)。

二、线程池详解

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

 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;  
    }  

我们详细解释下这些参数的含义:

看参数含义,我们最好不要自己去理解,看的时候要结合英文注释,因为注释部分是作者要表达的真正含义。

①corePoolSize: the number of threads to keep in the pool, even if they are idle.(核心线程池大小:为什么这么起名称,是因为作者想表达的含义是线程池应该保证线程数目会有这么多,不管他们创建以后是不是空闲)。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务

②maximumPoolSize: the maximum number of threads to allow in the pool.(线程池中允许创建线程的最大数目,啥意思?如果核心线程数已经达到,任务还源源不断的来,那么maximumPoolSize表达的意思就是核心线程数+新创建的线程数据<=最大线程数目)。

③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.(超过核心线程数的线程,在新任务到来之前能存活的最长时间,如果没等到,那么它就拜拜了)。
默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

④unit: the time unit for the keepAliveTime argument.(就是keepAliveTime 参数的单位),有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒

⑤BlockingQueue workQueue:the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.(等待执行的任务队列,它的作用就是用来存放还没有被执行的任务。这个队列仅仅用来存放被execute方法提交的Runnable任务,不要翻译成工作队列,那是给自己找麻烦)

⑥threadFactory:the factory to use when the executor creates a new thread.(线程工厂,用来创建新线程的工厂,一般使用默认工厂–Executors.defaultThreadFactory(),它是Executors类中的一个静态内部类的方法,返回一个实体类DefaultThreadFactory),用来创建线程

⑦RejectedExecutionHandler handler: the handler to use when execution is blocked because the thread bounds and queue capacities are reached.(当任务队列中任务已经达到了等待执行任务的队列的大小[有界队列会出现]时,要采取什么样的处理(handler)方式,取名叫拒绝策略,就是我应该怎么抛弃你)
有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值