Java多线程之线程池面试无压力(一)

公众号搜索: 意姆斯Talk, 即可领取大量学习资料及实战经验
在这里插入图片描述

1. 概念

1.1单线程的优缺点

本文的中心不是单线程, 一句话带过: 只有累死的牛(一个线程), 没有耕坏的地(任务)
优点: 如果任务不多, 一个线程, 毫无疑问, 也是可以提高性能
缺点: 创建线程, 会频繁的去访问操作系统, 又要自己手动创建和销毁, 浪费资源, 没有一个上限的束缚, 影响性能, 浪费的是创建和销毁的这个时间!!!

1.2 线程池的简述

作用: 限制了一个线程数量, 对线程进行统一的分配,调优和监控


优点和缺点

优点: 降低资源消耗, 提高响应速度, 提高线程的可管理性, 提供定期执行的线程池, 目前没有代替线程池的东西, 我愿意称之为百利而无一害

缺点: 如何去精准地把我线程池的各个参数, 是我开发中遇到的最大问题之一!


线程池解决的是什么?

资源管理和分配问题, 为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。


2. 线程池核心设计(图非原创)

**话不多说, 数形结合是最好的老师,此处图非原创, 实属精品, 只是精简了一下, 尊重原创, 文章尾部会附加原创链接 **
在这里插入图片描述
公司有 正式员工兼职员工 ,正常任务都是正式员工处理。当任务增加时,正式员工忙不过来了,公司会将订单暂时存到 仓库 中,等有空闲的正式员工时再处理(因为员工大部分是摸鱼空闲的, 也不会主动帮公司分担任务,所以需要管理员实时调度)。仓库满了后,任务还在增加怎么办?公司只能临时招 兼职员工 来应对任务,而兼职人员结束后是要清退的,若兼职员工也以招满后,后面的订单只能忍痛拒绝了。

管理员实时调度是源码中的getTask()方法, 下文会表明

原图如下:
在这里插入图片描述

好了接下来看看线程池的流程图配代码实例!!!

ThreadPoolExecutors.execute(new Runable(){......}); //表示一个当前线程数

for(int i=0;i<5;i++){
ThreadPoolExecutors.execute(new Runable(){......}); //表示五个当前线程数
}

在这里插入图片描述

2.1Java中的线程池核心实现类是ThreadPoolExecutor

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2ThreadPoolExecutor四种构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
 }
 
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
}

//核心构造方法
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();
        //执行终结器时要使用的上下文,或者为null, acc是个对象, 可看源码
        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(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
    unit(必需):指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

3. 线程池的两大参数之生命周期,任务队列, 拒绝策略

3.1生命周期(一张图概括)

在这里插入图片描述

  • RUNNING:当创建线程池后,初始时,线程池处于RUNNING状态;

  • SHUTDOWN: 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

  • STOP: 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

  • TERMINATED: 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

在这里插入图片描述

线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount), 在源码中常见的两个变量, 代表性代码:

通过状态和线程数生成ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

在这里插入图片描述

3.2任务队列

队列: 先进先出

队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
在这里插入图片描述

3.3拒绝策略

任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。策略是一个接口,

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
  1. AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
  2. CallerRunsPolicy:直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。
  3. DiscardPolicy:直接丢弃任务,不抛出任何异常。
  4. DiscardOldestPolicy:将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。
  5. 在这里插入图片描述

4. Executors封装线程池

1、FixedThreadPool

固定容量线程池。其特点是最大线程数就是核心线程数,意味着线程池只能创建核心线程,keepAliveTime为0,即线程执行完任务立即回收。任务队列未指定容量,代表使用默认值Integer.MAX_VALUE。适用于需要控制并发线程的场景

2、 SingleThreadExecutor
单线程线程池。特点是线程池中只有一个线程(核心线程),线程执行完任务立即回收,使用有界阻塞队列(容量未指定,使用默认值Integer.MAX_VALUE)

3、 ScheduledThreadPool
定时线程池。指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列。这是一个比较特别的线程池,适用于执行定时或周期性的任务

4、CachedThreadPool: 缓存线程池。没有核心线程,普通线程数量为Integer.MAX_VALUE(可以理解为无限),线程闲置60s后回收,任务队列使用SynchronousQueue这种无容量的同步队列。适用于任务量大但耗时低的场景。**

5. 线程池源码解读

待续…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值