[java 并发]你真的了解线程池吗?

为什么

线程池预先创建若干数量的线程,用户不能直接对线程的创建进行控制,重复使用固定或较为固定的线程来完成任务执行,消除了频繁创建和消亡线程的资源开销,面对过量任务的提交也能够平缓劣化

线程池种类

  1. Executors.newCachedThreadPool()
    可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,智能的添加新线程来执行。

    队列:LinkedBlockingDeque - 由链表结构组成的双向阻塞队列

  2. Executors.newFixedThreadPool(nThreads)

    固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,后面进入等待队列直到前面的任务完成才继续执行。

    队列: ArrayBlockingQueue -由数组构成的有界阻塞队列

  3. Executors.newSingleThreadExecutor()

    单个线程的线程池,线程池中每次只有一个线程工作,单线程串行执行任务。

    队列:SychronousQueue - 不存储元素的阻塞队列,单个元素的队列

  4. newScheduleThreadExecutor()

    大小无限制的线程池,支持定时和周期性的执行线程。

    队列:DelayQueue - 使用优先级队列实现的延迟无界阻塞队列

线程池的核心方法是如下的ThreadPoolExecutor

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

corePoolSize :线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收

maximumPoolSize :线程池中可以容纳的最大线程的数量

keepAliveTime :线程池中除了核心线程之外的其他的最长可以保留的时间,在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间

unit:非核心线程可以保留的最长的空闲时间的一个单位

workQueue:等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFO原则

threadFactory:创建线程的线程工厂

handler:一种拒绝策略,我们可以在任务满了之后拒绝执行某些任务。

拒绝策略一共有四种:

  • 第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
  • 第二种DisCardPolicy:不执行新任务,也不抛出异常
  • 第三种DisCardOldSetPolicy: 将消息队列中的第一个任务丢弃
  • 第四种CallerRunsPolicy:直接调用execute来执行当前任务,会降低新任务的提交速度,如果能够接受延迟并要求每个任务都执行可以选择这个策略

线程池五种状态

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));		
  • 线程池创建之后就处于 RUNNING 状态

  • 调用 shutdown() 方法之后处于 SHUTDOWN 状态,此时线程池不再接受新的任务,清除一些空闲 worker ,等待阻塞队列的任务完成

  • 调用 shutdownNow() 方法后处于 STOP 状态,此时线程池不再接受新的任务,中断所有的线程,阻塞队列中没有被执行的任务也会被全部丢弃

  • 当线程池中执行的任务为空时,也就是此时 ctl 的值为 0 时,线程池会变为 TIDYING 状态,接下来会执行 terminated() 方法

  • 执行完 terminated() 方法之后,线程池的状态就由 TIDYING 转到 TERMINATED 状态

在这里插入图片描述

线程池的工作过程

在这里插入图片描述
核心的Execute方法代码:

private static int workerCountOf(int c) {
    return c & CAPACITY;
}

private final BlockingQueue<Runnable> workQueue;

public void execute(Runnable command) {
    // 如果任务为null,则抛出异常。
    if (command == null)
        throw new NullPointerException();
    // ctl 中保存的线程池当前的一些状态信息
    int c = ctl.get();

    //  下面会涉及到 3 步 操作
    // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
    // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
    // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
        if (!isRunning(recheck) && remove(command))
            reject(command);
            // 如果当前线程池为空就新创建一个线程并执行。
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //3. 如果放入workQueue失败,尝试通过创建非核心线程来执行任务
    //如果还是失败,说明线程池已经关闭或者已经饱和,则通过reject()执行相应的拒绝策略的内容。
    else if (!addWorker(command, false))
        reject(command);
}

判断两次线程池的状态:多线程环境下线程池状态是时刻发生变化的,可能刚获取状态后就变化了,所以需要二次检查,如果没有二次检查的话,线程池处于非RUNNING状态时,command就永远不会执行

execute方法和submit方法的区别

  • execute 参数runnable ;submit参数 runnalbe 或runnable 和结果T或 Callable
  • execute没有返回值,submit有返回值 可以拿到线程返回的结果
  • submit的返回值future调用get方法可以捕获异常

submit 底层调用的也是execute方法,只是提交的任务不是task而是在task的基础上封装一层FutureTask,在执行过程中不会抛出异常,而是将变量存在成员变量中,futuretask.get获取的时候才会抛出。

spring的@Schedule 注解内部实现就是用submit,如果够贱的任务内部有未检查异常,永远也拿不到异常

execute直接抛出异常后就死掉,submit保存异常没有死掉,因此execute的线程池会出现没有意义的情况,因为线程没有重用,而submit不会有这种情况。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

荼白z

感谢老板请我喝咖啡

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

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

打赏作者

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

抵扣说明:

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

余额充值