(个人笔记向)线程池的简单概述

个人总结笔记,不具备指导作用,怕说错了说漏了带坏其他同学,有错误的地方希望热心的同学可以指出,感谢

进入正片

相信各位同学无论是在AS或者Idea中都碰到这种情况,当你安装阿里Java编程规范插件之后,每当你通过new Thread 的方式创建一个新线程的时候,编译器都会给你提示这么一段提醒:

编译器提醒我们不要显示的创建线程,推荐使用线程池。

那么为什么要使用线程池呢?

1、降低资源的消耗,通过线程的复用降低线程创建和销毁造成的消耗。

2、提高响应效率,当我们存在一个任务需要执行的时候,该任务无需等待线程的创建就可以立即执行,一定程度上节省了线程的创建和销毁的时间。

3、提高了线程的可控性,对线程数量上进行把控,避免不必要的资源消耗,对过多的任务请求也能进行调配优化(使用RejectedExecutionHandler)

介绍完毕使用线程池的原因,接下来说说使用线程池需要知道哪些基本概念,由上图可知,线程池的创建通过新建ThreadPoolE-xecutor实现,任务的执行通过execute()方法,关闭通过shutdown()方法。

首先看看ThreadPoolExecutor的构造方法

ThreadPoolExecutor(int corePoolSize,//1
                              int maximumPoolSize,//2
                              long keepAliveTime,//3
                              TimeUnit timeUnit,//4
                              BlockingQueue<Runnable> workQueue,//5
                              ThreadFactory threadFactory,//6
                              RejectedExecutionHandler rejectedExecutionHandler//7)

1、corePoolSize

线程池中的核心线程数,当线程池中目前的线程数小于核心线程数时,新建的任务会由当前的的核心线程进行执行,如果当前线程都在执行任务中,那么就会新建线程执行任务,直到当前线程数等于核心线程数。如果这时又进来新的任务,那么该任务会被塞入阻塞队列中,等待被执行。调用prestartAllCoreThreads()方法可以提前创建所有核心线程数,prestartAllCoreThreads()的逻辑如下所示。

    /**
     * Starts all core threads, causing them to idly wait for work. This
     * overrides the default policy of starting core threads only when
     * new tasks are executed.
     *
     * @return the number of threads started
     */
    /**
     * 简单翻译一下,第一句就可以说明:启动所有核心线程,使他们等待任务。
     */
    public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))
            ++n;
        return n;
    }

2、maximumPoolSize

线程池中允许存在的最大线程数,如果阻塞队列也排满了任务,那么这时新进来的任务,就会创建新的线程执行任务直到线程数与最大线程数相等。

3、keepAliveTime

线程空闲时的存活时间,当线程将任务执行完毕后,允许线程存活的时间(存活的意义是为了线程的复用),此参数只在线程数大于corePoolSize时才能用,并且核心线程不会被销毁,只有非核心线程有可能被销毁,也就是最多可移除maximumPoolSize-corePoolSize个线程

4、timeUnit

这个参数只是定义keepAliveTime参数的时间单位

5、workQueue

实现BlockingQueue接口的阻塞队列,一般有ArrayBlockingQueue和LinkedBlockingQueue,当线程数超过核心线程数时,线程会进入阻塞队列进行阻塞等待。

6、threadFactory

线程工厂,通过自定义的线程工厂可以为线程命名,一般情况下使用Executors.defaultThreadFactory()默认的线程工厂就可以,默认的命名规则为:"pool-" + poolNumber.getAndIncrement() + "-thread-" + threadNumber.getAndIncrement()

7、rejectedExecutionHandler

线程池的拒绝规则,目的:当线程池内所有可用线程资源都已耗尽(核心线程、非核心线程、阻塞队列都已经塞满了),就需要采取一种方式处理接下来还想要添加进来的任务,线程池提供了四种规则:

①AbortPolicy:直接抛出异常,默认规则。

②CallerRunsPolicy:用调用者所在的线程来执行任务。

③DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务。

④DiscardPolicy:直接丢弃任务。

实际调用

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                1,
                1,
                1,
                TimeUnit.SECONDS,
                arrayBlockingQueue,
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
poolExecutor.execute(() -> System.out.println("println" + n));

常见的四种ThreadPoolExecutor

CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor

他们之前的差别是对参数的不同配置来适用于不同的应用场景。

分析完毕基本参数的意义,接下来讲讲任务的执行顺序

查看ThreadPoolExecutor.execute()方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //获取线程池状态
        int c = ctl.get();
        //首先判断当前运行的线程是否少于corePoolSize,如果小于则尝试创建新的线程来执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))//判断是否成功添加,如果失败则继续第二个if判断
                return;//添加成功则返回
            c = ctl.get();//添加失败再次获取线程池状态
        }
        //第一步添加失败后,往阻塞队列添加任务
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //第二次检测线程池是否在运行,因为添加过程中线程池状态也许会改变
            if (! isRunning(recheck) && remove(command))
                //线程不在运行,队列移除刚刚添加的任务,并调用RejectedExecutionHandler执行拒绝规                                            
                //则
                reject(command);
            //判断当前线程数是否为0,因为核心线程数也有可能为0,因此当前线程数可能存在0的情况
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        //添加阻塞队列失败,会尝试新建非核心线程执行任务
        }else if (!addWorker(command, false))
            //非核心线程创建失败,调用RejectedExecutionHandler执行拒绝规则
            reject(command);
    }

通过源码可以大概明白ThreadPoolExecutor.execute()执行时任务添加顺序:核心线程优先、阻塞队列其次、非核心线程最后。

线程池的合理选择:

待补充(因为应该怎么设置核心数、阻塞队列应该选有界的ArrayBlockingQueue还是无界的LinkedBlockingQueue、非核心数还没有一个比较明显的应用场景,以后再补充吧,以及还有AddWorker方法的解析,这个方法中涉及到CAS的相关知识,不是特别明白,还是要在学习学习。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值