ThreadPoolExecutor线程池

创建线程池-适用线程池-关闭线程池


ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2,//核心线程数
    4,// 总线程数
    10,// 救急线程存活时间
    TimeUnit.SECONDS,//救急线程存活时间的单位
    new LinkedBlockingDeque<>(10),//阻塞队列
    Executors.defaultThreadFactory(),// 线程工厂
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
for (int i = 0; i < 10; i++) {
    pool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + " is executing task");
        try {
            Thread.sleep(1000); // 模拟任务执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
// 关闭线程池
pool.shutdown();
try {
    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
        pool.shutdownNow();
    }
} catch (InterruptedException e) {
    pool.shutdownNow();
}
}

ThreadPoolExecutor 构造函数

主要是进行核心参数的设置

public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
    // ctl 这是一个 AtomicInteger 类型的变量,存储了线程池的状态, 还存储当前线程池中线程数的大小
    this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
    // mainLock 这是一个 ReentrantLock 类型的锁,用于保护线程池的状态。
    this.mainLock = new ReentrantLock();
    // workers 这是一个 HashSet 类型的集合,用于存储工作线程。
    this.workers = new HashSet();
    // termination 这是一个 Condition 类型的条件变量,用于线程池的终止操作
    this.termination = this.mainLock.newCondition();
    if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
        if (var6 != null && var7 != null && var8 != null) {
            // AccessControlContext 是 Java 安全管理器(Security Manager)的一部分,用于保存当前线程的访问控制上下文。
            this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
            // 核心线程数
            this.corePoolSize = var1;
            // 最大线程数
            this.maximumPoolSize = var2;
            // 阻塞队列/工作队列
            this.workQueue = var6;
            // 救急线程的存活时间
            this.keepAliveTime = var5.toNanos(var3);
            // 线程工厂
            this.threadFactory = var7;
            // 拒绝策略
            this.handler = var8;
        } else {
            throw new NullPointerException();
        }
    } else {
        throw new IllegalArgumentException();
    }
}

可以见得,在构造器中,还有一个特殊的属性 acc

execute(Runnable var1)

用于提交一个任务到线程池中去执行

public void execute(Runnable var1) {
    if (var1 == null) {
        throw new NullPointerException();
    } else {
        // 获取当前线程池的状态码
        int var2 = this.ctl.get();
        // workerCountOf(var2) 获取线程池中线程的数量
        // 如果当前线程数 < 核心线程数 则会调用addWorker()方法去添加一个新的工作线程来执行任务
        if (workerCountOf(var2) < this.corePoolSize) {
            if (this.addWorker(var1, true)) {
                return;
            }
            // 执行完任务获取当前线程池状态码
            var2 = this.ctl.get();
        }
    //到这是 当前线程数 > 核心线程数
        // isRunning() 检查线程池是否正在运行
        // workQueue.offer(var1) 将任务添加 阻塞队列中
        if (isRunning(var2) && this.workQueue.offer(var1)) {
            // 当前线程正在运行 且 阻塞队列未满 当前任务成功添加到阻塞队列中
            int var3 = this.ctl.get();
            // 判断线程池状态 如果不是运行时状态了 把刚刚添加的任务移除,走拒绝策略
            if (!isRunning(var3) && this.remove(var1)) {
                this.reject(var1);
            } else if (workerCountOf(var3) == 0) { // 如果线程池空了,会创建一个新的线程去等待任务
                this.addWorker((Runnable)null, false);
            }

        // 如果线程池不在运行状态 则会去添加救急线程去处理任务, 添加成功处理任务成功直接返回
        } else if (!this.addWorker(var1, false)) {
            // 如果 添加工作线程失败(当前线程数 不小于 最大线程数) 无法创建救急线程
            // 执行拒绝策略
            this.reject(var1);
        }
    
    }
}
  1. 当前线程数 < 核心线程数 创建核心线程执行任务
  2. 当前线程数 > 核心线程数 将任务添加到阻塞队列中
  3. 当阻塞队列中满了之后,创建救急线程,处理阻塞队列中的任务
  4. 当前线程数 > 最大线程数,则走拒绝策略

addWorker(Runnable var1, boolean var2)

用于想线程池中添加一个新的工作线程
var1 是 线程任务
var2 是 标志,true 则是要添加 核心线程 false则是要添加 非核心线程

private boolean addWorker(Runnable var1, boolean var2) {
    while(true) {
        // 检查线程池状态
        int var3 = this.ctl.get();
        int var4 = runStateOf(var3);
        if (var4 >= 0 && (var4 != 0 || var1 != null || this.workQueue.isEmpty())) {
            return false;
        }
        
        while(true) {
            // 获得当前线程数
            int var5 = workerCountOf(var3);
            // var5 >= 536870911  是线程池中可容纳的最大的线程数量 2^29 个
            // var2 是true 则是核心线程
            // var2 是false则是救急线程
            // 核心线程:  如果 当前线程数 < 核心线程数 则可以创建核心线程  否则返回false
            // 救急线程:  如果 当前线程数 < 最大线程数 则可以创建救急线程	 否则返回false
            if (var5 >= 536870911 || var5 >= (var2 ? this.corePoolSize : this.maximumPoolSize)) {
                return false;
            }
            // 尝试增加线程池中的线程数量 
            if (this.compareAndIncrementWorkerCount(var3)) {
                boolean var18 = false;
                boolean var19 = false;
                
                ThreadPoolExecutor.Worker var20 = null;

                try {
                    // 创建一个worker对象 封装任务和线程
                    var20 = new ThreadPoolExecutor.Worker(var1);
                    Thread var6 = var20.thread;
                    
                    if (var6 != null) {
                        ReentrantLock var7 = this.mainLock;
                        var7.lock();

                        try {
                            int var8 = runStateOf(this.ctl.get());
                            //。。。。。 省略
                            // 将 worker添加到workers 这个hashset中
                                this.workers.add(var20);
                            //。。。。。 省略
                        } finally {
                            var7.unlock();
                        }

                        if (var19) {
                            // 启动线程 执行线程的run方法
                            var6.start();
                            var18 = true;
                        }
                    }
                } 
                //。。。。。 省略

                return var18;
            }
            //。。。。。 省略
        }
    }
}
  1. 判断想要增加的线程是 核心线程 还是 救急线程
    1. 如果是核心线程 判断 当前线程数 是否小于 核心线程数 小于则可以创建核心线程,否则则返回false
    2. 如果是非核心线程,判断 当前线程数 是否小于 最大线程数 小于则可以创建非核心线程(救急线程) 否则返回false
  2. 创建worker对象,封装任务runnable,并将worker添加到 workers的hashset集合中
  3. Worker.Thread.start() 启动线程执行run方法,就会去执行worker的run方法

worker实现了runnable接口,重写了run方法

Worker对象

Worker类 实现了Runnable接口,因此任务可以提交给线程池执行,重写run 方法,线程池执行任务会调用worker的run方法
在run方法中调用了runWorker() 这个方法
并继承了AQS会有一些并发的性质

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    private static final long serialVersionUID = 6138294804551838833L;
    final Thread thread;   // 线程
    Runnable firstTask;		// 任务
    volatile long completedTasks;  // 用于记录工作线程已完成的任务数量

    Worker(Runnable var2) {
        this.setState(-1);
        this.firstTask = var2;
        this.thread = ThreadPoolExecutor.this.getThreadFactory().newThread(this);
    }
    // run 方法
    public void run() {
            ThreadPoolExecutor.this.runWorker(this);
        }

}

runWorker(ThreadPoolExecutor.Worker var1)

用于执行工作线程中的任务

final void runWorker(ThreadPoolExecutor.Worker var1) {
    Thread var2 = Thread.currentThread();
    Runnable var3 = var1.firstTask;
    var1.firstTask = null;
    var1.unlock();
    boolean var4 = true;

    try {
        // 第一次进来 当前任务 var3(runnable) 不为空,是当前要执行的任务runnable,直接进入循环
        // 第二次来, 去通过getTask() 在阻塞队列中去获取任务
        while(var3 != null || (var3 = this.getTask()) != null) {
            // 判断线程池状态
            var1.lock();
            if ((runStateAtLeast(this.ctl.get(), 536870912) || Thread.interrupted() && runStateAtLeast(this.ctl.get(), 536870912)) && !var2.isInterrupted()) {
                var2.interrupt();
            }

            try {
                // 执行线程之前的钩子函数
                this.beforeExecute(var2, var3);
                Object var5 = null;

                try {
                    // 这里直接调用 这个Runnable 的run 方法 而不启动一个线程
                    var3.run();
                } 
                // 。。。。省略
                } finally {
                    // 执行线程后的钩子函数
                    this.afterExecute(var3, (Throwable)var5);
                }
            } finally {
                // 每次执行完任务后 var3 runnable这个任务会置为空
                var3 = null;
                // 已完成线程数 +1 用来统计整个线程池中已经完成了的线程数
                ++var1.completedTasks;
                var1.unlock();
            }
        }

        var4 = false;
    } finally {
        this.processWorkerExit(var1, var4);
    }

}
  1. 第一次执行,当前任务不为空,进入循环
  2. 检查线程池状态
  3. 执行线程之前的钩子函数this.beforeExecute(var2, var3)
  4. 调用runnable 任务的run 方法,执行任务
  5. 执行线程后的钩子函数 this.afterExecute(var3, (Throwable)var5);

getTask()

private Runnable getTask() {
    boolean var1 = false;

    while(true) {
        int var2 = this.ctl.get();
        int var3 = runStateOf(var2);
        // 检查线程池状态
        if (var3 >= 0 && (var3 >= 536870912 || this.workQueue.isEmpty())) {
            this.decrementWorkerCount();
            return null;
        }
        // 当前线程数
        int var4 = workerCountOf(var2);
        // allowCoreThreadTimeOut 如果为true 则核心线程 会被销毁 
        // 否则当前线程数大于核心线程数才会被销毁
        // var5 为 true 则当前线程 可以被销毁 false 则不能被销毁
        boolean var5 = this.allowCoreThreadTimeOut || var4 > this.corePoolSize;
        
        if (var4 <= this.maximumPoolSize && (!var5 || !var1) || var4 <= 1 && !this.workQueue.isEmpty()) {
            try {
                // 从等待队列中拿到任务
                // 如果当前线程可以被销毁 poll(this.keepAliveTime, TimeUnit.NANOSECONDS) 从任务队列中获取新任务,
                //  并等待keepAliveTime时间,如果任务队列在等待时间内没有新任务,则返回null
                //如果当前线程不可以被销毁  workQueue.take() 调用take去阻塞队列中拿任务,并一直等到新任务到来 
                Runnable var6 = var5 ? (Runnable)this.workQueue.poll(this.keepAliveTime, TimeUnit.NANOSECONDS) : (Runnable)this.workQueue.take();
                if (var6 != null) {
                    return var6;
                }

                var1 = true;
            } catch (InterruptedException var7) {
                var1 = false;
            }
        } else if (this.compareAndDecrementWorkerCount(var2)) {
            return null;
        }
    }
}
  1. 判断当前线程是否可以被销毁,调用allowCoreThreadTimeOut(true) 即可将核心线程数也设置为可销毁,
  2. 如果可以被销毁,调用 workQueue.poll(this.keepAliveTime, TimeUnit.NANOSECONDS)从任务队列中获取新任务,并等待keepAliveTime时间,如果任务队列在等待时间内没有新任务,则返回null
  3. 如果不可以被销毁,则调用workQueue.take()去阻塞队列中拿任务,并一直等到新任务到来

问题总结

什么是线程池

线程池是一种池化技术的实现,池化技术的核心思想其实就是实现资源的一个复用,避免资源的重复创建和销毁带来的性能开销。在线程池中,线程池可以管理一堆线程,让线程执行完任务之后不会进行销毁,而是继续去处理其它线程已经提交的任务。

使用线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控

线程池的构造参数

  • corePoolSize:线程池中用来工作的核心的线程数量。
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数
  • keepAliveTime:超出 corePoolSize 后创建的线程存活时间或者是所有线程最大存活时间,取决于配置。
  • unit:keepAliveTime 的时间单位。
  • workQueue:任务队列,是一个阻塞队列,当线程数已达到核心线程数,会将任务存储在阻塞队列中。
  • threadFactory :线程池内部创建线程所用的工厂。
  • handler:拒绝策略;当队列已满并且线程数量达到最大线程数量时,会调用该方法处理该任务。

刚创建出来的线程池中有没有线程

刚创建出来的线程池中只有一个阻塞队列,此时没有任何线程。
如果想要在执行任务之前创建好核心线程,可以调用prestartAllCoreThreads() 方法来提前创建核心线程
并且在核心线程没有创建满之前,是一个一个去创建线程的。

线程池的运行原理

  1. 判断 当前线程数是否小于核心线程数,小于则创建核心线程来执行当前任务,当任务执行完之后,线程不会退出,而是会去从阻塞队列中获取任务
  2. 如果当前线程数 不小于 核心线程数,则会将 当前任务放入阻塞队列中,
  3. 如果阻塞队列已满,则会判断当前线程数是否小于最大线程数,满足则会去创建救急线程去执行任务,救急线程会存活一段时间,去从阻塞队列中获取任务,存活时间过后则会自行销毁
  4. 如果当前线程数 不小于 最大线程数 ,即无法创建新的线程了,则会走拒绝策略

拒绝策略

RejectedExecutionHandler的实现JDK自带的默认有4种

  • AbortPolicy:丢弃任务,抛出运行时异常
  • CallerRunsPolicy:由提交任务的线程来执行任务
  • DiscardPolicy:丢弃这个任务,但是不抛异常
  • DiscardOldestPolicy:从队列中剔除最先进入队列的任务,然后再次提交任务

线程池创建的时候,如果不指定拒绝策略就默认是AbortPolicy策略。当然,你也可以自己实现RejectedExecutionHandler接口,比如将任务存在数据库或者缓存中,这样就数据库或者缓存中获取到被拒绝掉的任务了。

线程池如何实现线程复用的

在runWorker 方法内部使用了while死循环,当第一个任务结束之后,会不断的通过getTask() 方法获取任务,只要能获取到任务,就会调用 当前任务的run 方法,继续执行这个任务

在执行run方法之前 有一个钩子函数beforeExecute() 可以重写这个方法来执行一些任务
在执行run方法之后 有一个钩子函数afterExecute() 可以重写这个方法来执行一些任务

核心线程会不会被销毁

默认情况下allowCoreThreadTimeOut是为false,核心线程即使在空闲的状态下也不会被销毁,会一直存活,直到线程关闭
当调用allowCoreThreadTimeOutt(true) 方法 后,核心线程则会和 救急线程一样在等待一定的存活时间后销毁
这样允许线程池在没有任务执行时减少资源消耗

核心线程和非核心线程的区别

他俩本质上没有区别,都是普通的线程 Thread
在线程池中,来新任务,如果需要创建线程,则会调用addWorker(runnable,boolean) 方法,如果创建核心线程,则是addWorker(runnable,true)
如果创建非核心线程则是addWorker(runnable,false)
然后通过worker对象将任务runnable封装,再创建一个线程,worker重写了run方法

线程是如何获取任务的,以及,如何实现超时销毁的

线程池通过调用getTask()方法,从阻塞队列中获取任务

  1. 判断当前线程是否可以被销毁 (allowCoreThreadTimeOut是否为true 或者 当前线程数大于核心线程数,则会被销毁)
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
  1. 如果是可以销毁的,则调用 workQueue.poll(this.keepAliveTime, TimeUnit.NANOSECONDS)方法,从阻塞队列里获取任务,并且等待keepAliveTime,如果没有获取到任务,就会被销毁
  2. 如果是不可以被消耗的,则调用workQueue.take()去阻塞队列中拿任务,并一直等到新任务到来 ,(拿不到任务一直阻塞)

线程池的五种状态

  • RUNNING:线程池创建时就是这个状态,能够接收新任务,以及对已添加的任务进行处理。
  • SHUTDOWN:调用shutdown方法线程池就会转换成SHUTDOWN状态,此时线程池不再接收新任务,但能继续处理已添加的任务到队列中任务。
  • STOP:调用shutdownNow方法线程池就会转换成STOP状态,不接收新任务,也不能继续处理已添加的任务到队列中任务,并且会尝试中断正在处理的任务的线程。
  • TIDYING:
    SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态。
    线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池会变为 TIDYING 状态。
    线程池在 STOP 状态,线程池中执行中任务为空时,线程池会变为 TIDYING 状态。
  • TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会转变为 TERMINATED 状态。

线程池状态具体是存在ctl成员变量中,ctl中不仅存储了线程池的状态还存储了当前线程池中线程数的大小

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

如何存储的
**int 一共32 为 用29为存储线程池的当前线程数,所以,线程池最多可以创建2^29 -1 个线程。其高3位用来存储 状态 **

如何优雅的关闭线程池

  1. 调用shutdown();会继续执行正在运行的任务和队列中等待的任务然后再关闭
  2. 等待了一段时间,调用awaitTermination(shutDownTimeOut, timeUnit)检查是否在时间内关闭
  3. 没有关闭则调用shutdownNow();进行强行关闭
pool.shutdown();
try {
    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
        pool.shutdownNow();
    }
} catch (InterruptedException e) {
    pool.shutdownNow();
}

如何设置核心线程数

线程数设置的理论:

  1. CPU 密集型的程序 - 核心数 + 1
  2. I/O 密集型的程序 - 核心数 * 2理论公式在书中的定义《Java 并发编程实战》介绍了一个线程数计算的公式:Ncpu=CPU核心数Ucpu=目标CPU利用率,0<=Ucpu<=1w/c=等待时间和计算时间的比例如果希望程序跑到CPU的目标利用率,需要的线程数公式为:Nthreads= Ncpu*Ucpu(1+w/c)如果我期望目标利用率为90%(多核90),那么需要的线程数为:核心数12 * 利用率0.9 * (1 +50(sleep时间)/50(循环50_000_000耗时)) ≈ 22照上面的公式来规划线程数的话,误差一定会很大。因为此时这台主机上,已经有很多运行中的线程了,Tomcat有自己的线程池,HikariCP也有自己的后台线程,JVM也有一些编译的线程,连G1都有自己的后台线程。这些线程也是运行在当前进程、当前主机上的,也会占用CPU的资源。没有固定答案,先设定预期,比如我期望的CPU利用率在多少,负载在多少,GC频率多少之类的指标后,再通过测试不断的调整到一个合理的线程数流程一般是这样:
  3. 分析当前主机上,有没有其他进程干扰;分析当前JVM进程上,有没有其他运行中或可能运行的线

  1. 设定目标

目标CPU利用率 - 我最高能容忍我的CPU飙到多少?目标GC频率/暂停时间 - 多线程执行后,GC频
率会增高,最大能容忍到什么频率,每次暂停时间多少?

  1. 执行效率 - 比如批处理时,我单位时间内要开多少线程才能及时处理完毕
  2. ……根据预期,套用公式获取一个大概数值,再进行不断测试
  3. 梳理链路关键点,是否有卡脖子的点,因为如果线程数过多,链路上某些节点资源有限可能会导致

大量的线程在等待资源(比如三方接口限流,连接池数量有限,中间件压力过大无法支撑等)

  1. 不断的增加/减少线程数来测试,按最高的要求去测试,最终获得一个“满足要求”的线程数

很多的内部业务系统,并不需要啥性能,稳定好用符合需求就可以了。那么我的推荐的线程数是:CPU
核心数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值