Java 基础篇之线程池

Java 基础篇之线程池

  • 线程池的主要工作流程是什么?

请添加图片描述

核心代码:ThreadPoolExecutor类

public void execute(Runnable command) {
    //如果任务为null,抛出空指针异常
    if (command == null)
        throw new NullPointerException();
    /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
    //获取当前线程池的状态+线程个数变量的组合值
    int c = ctl.get();
    //1.如果当前有效线程数 < 核心线程数,调用addWoker执行任务(即创建一个线程执行任务)
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2.如果当前有效线程数 > 核心线程数,并且当前线程池状态为运行状态,同时尝试非阻塞方法向任务队列放入任务(放入失败返回false)
    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.如果阻塞队列已满,则调用addWorker执行任务(即创建一条线程执行该任务)
    else if (!addWorker(command, false))
        //如果创建线程失败,则调用线程拒绝策略
        reject(command);
}
  • 线程池有哪几种工作队列?怎么理解无界队列和有界队列?

    三种队列:

    1. ArrayBlockingQueue:有界队列(基于数组的)
    2. LinkedBlockingQueue:有/无 界队列(基于链表的,传参就有界,不传参就无界[默认Integer.MAX_VALUE]
    3. SynchronousQueue:同步队列(不存储元素的阻塞队列)

    有界队列:

    有固定大小的队列。例如设定了固定大小的LinkedBlockingQueue,大小为0在生产者和消费者中做中转用的SynchronousQueue。

    无界队列:

    没有设置固定大小的队列。可以直接入列,直到溢出,默认为Integer.MAX_VALUE(2147483647).

  • 线程池的拒绝策略有何用途,有哪些拒绝策略?是否可以自定义拒绝策略?

    拒绝策略是对线程池起限流保护的作用。

    有4种拒绝策略:

    1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常

    2.ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常

    3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

    4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

    ​ 😃可以根据实际应用场景实现RejectedExecutionHandler接口,自定义拒绝策略,如记录日志或持久化存储不能处理的任务,便于定位问题,分析问题。

    public class Test {
    
        public static class MyTask implements Runnable {
            @Override
            public void run() {
                System.out.println("thread id:" + Thread.currentThread().getId());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args){
            LinkedBlockingDeque queue = new LinkedBlockingDeque(4);
            ExecutorService es = new ThreadPoolExecutor(
                    5,
                    5,
                    0L,
                    TimeUnit.MILLISECONDS,
                    queue,
                    Executors.defaultThreadFactory(),
                    new RejectedExecutionHandler() {
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            System.out.println("这里是自定义的线程拒绝策略");
                        }
                    }
            );
    
            MyTask task = new MyTask();
            for (int i=0; i<100; i++) {
                es.submit(task);
            }
        }
    }
    
    结果:
    thread id:12
    thread id:16
    这里是自定义的线程拒绝策略
    
  • 如何创建、停止线程池?为什么不建议使用Executors构建线程池?

    ① 线程池创建:

    ​ 1. 使用ThreadPoolExecutor创建,传递线程池的各种参数

    ​ 2. 使用Executors创建,调用不同的线程池类型(例如newFixedThreadPool)

    ② 线程池终止:

    ​ 1.优雅型退出-shutdown()

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

    ​ 2.强迫型退出-shutdownNow()

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

    ③ 为什么不建议使用Executors构建线程池

    ​ 因为Executors 提供的很多方法默认使用的都是无界的LinkedBlockingQueue,高负载情况下,无界队列很容易导致OOM(内存溢出),因此强烈建议使用有界队列。

  • 线程池有哪些种类,各自的使用场景是什么?

    ① newSingleThreadPool:单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行

    ② newFixedThreadPool:固定大小的线程池,用于已知并发压力的情况下,对线程数做限制

    ③ newCachedThreadPool:一个可缓存的线程池,比较适合处理执行时间比较小的任务

    ④ newScheduledThreadPool:适用于定时以及周期性执行任务的场景

    ⑤ newWorkStealingThreadPool:jdk1.8提供的线程池,底层使用ForkJoinPool实现,适用于大任务分解并行执行的场景。Fork/Join框架用到了工作窃取(work-stealing)算法,任务分割为若干互不依赖的子任务。

  • 线程池有哪些状态,状态的设计机制是什么,状态是如何相互切换的?

    ① 状态

    1. RUNNING:接受新任务并且处理阻塞队列里的任务

    2. SHUTDOWN:拒绝新任务但是处理阻塞队列里的任务

    3. STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理任务

    4. TIDYING:所有任务都执行完(包含阻塞队列里面的任务),当前线程池活动线程为0,将要调用terminated方法

    5. TERMINATED:终止状态,terminated方法调用完成以后的状态

      /* The runState provides the main lifecycle control, taking on values:
      *
      *   RUNNING:  Accept new tasks and process queued tasks
      *   SHUTDOWN: Don't accept new tasks, but process queued tasks
      *   STOP:     Don't accept new tasks, don't process queued tasks,
      *             and interrupt in-progress tasks
      *   TIDYING:  All tasks have terminated, workerCount is zero,
      *             the thread transitioning to state TIDYING
      *             will run the terminated() hook method
      *   TERMINATED: terminated() has completed
      /
      

    ② 状态设计机制

    请添加图片描述

    ​ 使用一个AtomicInteger类型的变量ctl,来封装线程池的状态以及起当前活动的线程数

    ​ ctl 一共由32位,其中高3位来表示线程池的状态:

    ​ 111:RUNNING

    ​ 000:SHUTDOWN

    ​ 001:STOP

    ​ 010:TIDYING

    ​ 011:TERMINATED

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
    // 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;
    

    ③ 状态是如何切换的

请添加图片描述

 /* RUNNING -> SHUTDOWN
     *    On invocation of shutdown(), perhaps implicitly in finalize()
     * (RUNNING or SHUTDOWN) -> STOP
     *    On invocation of shutdownNow()
     * SHUTDOWN -> TIDYING
     *    When both queue and pool are empty
     * STOP -> TIDYING
     *    When pool is empty
     * TIDYING -> TERMINATED
     *    When the terminated() hook method has completed
     /
  1. RUNNING -> SHUTDOWN:显式调用shutdown()方法,或者隐式调用了finalize(),它里面调用了shutdown()方法

  2. RUNNING or SHUTDOWN -> STOP:显式调用shutdownNow()方法

  3. SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候

  4. STOP -> TIDYING:当线程池为空的时候

  5. TIDYING -> TERMINATED:当terminated() 方法执行完毕之后

  • 线程池的使用场景,线程池为什么能提升性能?

    ① 使用场景

    ​ 适用于高并发,批量处理,性能调优等场景

    ② 为什么提升性能

    ​ 节省线程创建、销毁的时间

  • 线程池有哪些重要参数,如何设置这些重要参数?

    ① 重要参数

    1. corePoolSize:线程池的核心线程数,即使线程池里没有任何任务,也会有corePoolSize个线程在候着等任务
    2. maximumPoolSize:线程池的最大线程数,不管有多少任务,线程池最多工作线程数就是maximumPoolSize
    3. keepAliveTime:线程存活的时间,当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行时,则线程退出
    4. unit:用来指定keepAliveTime的单位
    5. workQueue:队列,提交的任务将会被放入这个队列里
    6. threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字
    7. handler:拒绝策略,当线程池里的线程被耗尽,且队列也满了的时候会调用

    ② 如何设定这些参数

    1. 参考Executors类设置,测试

    2. CPU密集型:计算密集型,大部分时间用来做计算逻辑判断等CPU操作,尽量减少CPU上下文切换,核心线程数大小 = Ncpu + 1 (N 几核)

    3. IO密集型:任务需要执行大量的IO操作,涉及到网络、磁盘IO操作,对CPU消耗较小,核心线程数=2Ncpu

  • 线程池如何获取执行返回的结果?

    ① 如果是同步获取结果:入口方法是execute(Runnable command)

    ② 如果是异步获取结果:ThreadPoolExecutor 类的 submit 方法

  • 出现unable to create new native thread的异常,如何分析解决?

    原因:

    ​ 创建线程数超过了操作系统的限制

    分析:

    ​ 线程数设置是否合理,是否存在多个定时调度任务,且线程数太大

    解决:

    ​ 设置合理的线程数,加机器等

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

图图学Java

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

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

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

打赏作者

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

抵扣说明:

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

余额充值