关于线程池,你所要了解的一些东西(一)

tech2022-07-31  130

文章首发于有间博客,欢迎大伙光临有间博客

线程池简介

在介绍线程池之前,我们先来了解一下为什么要使用多线程,比如我们每天放学回家,都要去写作业,并且有好几门作业如:语文、数学、英语…如果我们一门一门的去写,只能是写完语文再去写数学…那写完估计也就晚上了,这是单线程单核执行的情况,如果我们使用多线程,那就变成了我们需要同时写好几门的作业,语文写一点,然后再数学写一点,再回到语文继续写,加上作业换来换去的情况,效率说不定比单线程的时候更慢,这是多线程单核执行的情况。那如果是多核呢,那就相当于我们拥有了分身的功能,能在写语文的同时,另一个分身替我们写数学,等我们写完语文的时候,数学也写完了。这是多线程多核的情况。

所以如果我们要接连处理一些疑难杂症的时候可以调用多线程来进行处理。那线程是越多越好吗?越多就越快?这肯定是否定的,那具体一个任务需要创建几个线程来执行最好呢?要怎么控制这些线程防止多创建呢?…所以,我们可以通过线程池来进行对线程的调控,以及一些其他的功能,我们详细介绍。 ps:一切事物从不同的角度看,都会有两面性,如由于线程的上下文切换,单核多线程一定会比单线程慢吗…不一定,比如一个事件需要先进行IO的读取,再进行cpu的调用,但是cpu的速度太快,而IO的速度太慢,导致cpu在某一段时间都是等待的状态,所以我们使用多线程,能在这个事件等待IO数据读入的时候去处理下一个事件,合理利用cpu的资源。所以需要具体问题具体分析。

线程池的好处

线程池在程序启动的时候就创建若干线程来响应处理,线程池里面的线程叫工作线程,有如下好处   第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。   第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。   第三:提高线程的可管理性。 常用线程池有:   Executors.newSingleThreadExecutor()   Executors.newFixedThreadPool()   Executors.newCachedTheadPool() 但在开发中还是建议自己根据业务的实际情况创建线程池。

线程池介绍

ok,铺垫了这么多,我们从Executors 开始一步一步的深入线程池的了解。

//我们从这个方法开始看,看看Executors类是怎么做的 ExecutorService executorService = Executors.newCachedThreadPool(); //将这个类以及部分的方法进行展示,我们可以发现,不管是newCacheThreadPool还是别的线程池 //都是通过ThreadPoolExecutor()构造函数进行创建,只是传入的参数不同,从而导致了不同的线程池的产生 //我们先去对ThreadPoolExecutor这个类进行解析,再回来分析这些常用线程池的意义是什么。 public class Executors { public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } .... public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } .... public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } }

ThreadPoolExecutor介绍

根据上面的调用我们基本可以知道线程池就是由ThreadPoolExecutor 这个类产生的,根据传入不同的参数值而生成不同的线程池。 我们先看看ThreadPoolExecutor以及其顶层是怎么构成的

//ThreadPoolExecutor继承了AbstractExecutorService类,获得它的一些方法以及实现 //点开AbstractExecutorService可以看见他还被很多其他的线程池所继承 public class ThreadPoolExecutor extends AbstractExecutorService{} //AbstractExecutorService实现了ExecutorService接口,并对内部的方法进行实现 //ExecutorService接口内定义了许多线程池的基础方法,如shutdown(), shutdownNow(),submit()..等 public abstract class AbstractExecutorService implements ExecutorService {} //ExecutorService 继承了 Executor接口 //Executor接口内部只有一个最核心的执行方法,void execute(Runnable command); public interface ExecutorService extends Executor {}

我们重点来看ThreadPoolExecutor 的具体实现,先从构造方法开始看起,要击败敌人,首先的目标是要了解敌人。

/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. 使用给定的初始化参数创建一个线程池 * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * 设置了线程池的核心池大小,在不超过核心池大小的时候每有一个执行请求都会创建一个新的线程执行 * 并且核心池的线程不会超时过期,除非设置allowCoreThreadTimeOut为true。 * 设置为true后,核心池的线程在达到keepAliveTime后也会销毁 * @param maximumPoolSize the maximum number of threads to allow in the * pool * 线程池中的允许存在的最大线程数 * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * 当线程数大于核心池线程数的时候,这是多出来的线程在没有任务时存活的最大 * 时间 * @param unit the time unit for the {@code keepAliveTime} argument * 上述存活时间的时间单位 * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * 工作队列是在执行任务前保留任务的一个队列,这个队列将保留由execute提交的Runnable任务 * 是一种用于缓存的任务的排队策略 * @param threadFactory the factory to use when the executor * creates a new thread * 线程工厂是执行程序创建线程时所需要的创建工厂 * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * 由于达到了线程池的最大值上限与队列上限,所执行的拒绝策略, * 在执行被阻止时使用的处理程序 * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { //首先是先判断创建的核心池大小 >= 0, 并且线程池的最大线程数要 > 0,不然无法创建线程就会 //执行拒绝策略,其次是核心池的大小需要<=最大线程数,以及非核心池的空闲线程持续时间不能< 0 if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); //接着是对工作队列,线程工厂,以及拒绝策略的限定,不能为null。 if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); 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; }

以上是定义线程池的一些参数,然后再看看ThreadPoolExecutor在初始化定义的时候定义的一些重要参数信息。

//在后续的线程池执行中总会出现这个语句,这个很重要,定义了当前线程池的状态和活跃线程数 //一个值就定义了两个状态 int c = ctl.get();

int c = ctl.get(); 获取当前线程的状态值和线程数,先看看ctl是什么玩意

//可以看见ctl是一个AtomicInteger类型的对象,可以通过atomicInteger达到原子性的加减 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //传入两个int值进行做或运算,转化为二进制后,只有全部为0才为0,不然就为1. //对应上述的ctlOf(RUNNING, 0),我们提出一个猜想:更像是对RUNNING的状态值和初始化线程数0进行一个整合 //我们先看看RUNNING代表什么再说。 private static int ctlOf(int rs, int wc) { return rs | wc; } // runState is stored in the high-order bits //线程池的运行时状态存储在高位的比特中。 可以看见是-1向左进COUNT_BITS位 private static final int RUNNING = -1 << COUNT_BITS; //可以看见COUNT_BITS是Integer的size -3 = 29. private static final int COUNT_BITS = Integer.SIZE - 3; //通过到这里可以知道RUNNING的值是 11100000 00000000 00000000 00000000 //根据官方的解释,最高的三位可以代表当前线程池的运行状态,其他位数则可以代表当前线程池中的线程数量 //再来看看其他的线程池状态,仅看最高位即可 //shutdown的运行状态则为 00000000 00000000 00000000 00000000 private static final int SHUTDOWN = 0 << COUNT_BITS; //stop 的运行状态则为 00100000 00000000 00000000 00000000 private static final int STOP = 1 << COUNT_BITS; //tidying 的运行状态为 01000000 00000000 00000000 00000000 private static final int TIDYING = 2 << COUNT_BITS; //termination 的运行状态为 01100000 00000000 00000000 00000000 private static final int TERMINATED = 3 << COUNT_BITS; //可以看见除了RUNNNING,其他的状态都是>=0的,这在后面会有用 //介绍到这里,我们再来认识几个参数 //这是获取线程池的活跃线程的最大容量。 相当于是 stop的值 - 1 // 00100000 00000000 00000000 00000000 - 1 = 00011111 1111111 11111111 11111111 private static final int CAPACITY = (1 << COUNT_BITS) - 1; //所以以下两个方法,就可以获取到对应ctl对象的线程池状态与当前线程池活跃线程数 private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; }

所以可以总结一下,ctl为一个AtomicInteger对象,ctl.get()可以获取当前的线程池状态情况和线程数的值。runStateOf(int c) 与 workerCountOf(int c) 可以获取当前线程池的状态与活跃线程数。 ctl的前三位为线程池的状态值和其他位组成的线程池活跃线程数。

总结

很基本的介绍了一下线程池的构造和基础参数的设置。 总的来说如果我们需要使用线程池,在平时用用的话可以直接使用Executors创建使用,如果是在开发中,建议还是手动进行ThreadPoolExecutor的创建。 剩下的内容,比如线程池的执行流程,内容过多…就放在下一篇一起写了。

最新回复(0)