线程池_线程池详解

article/2025/9/10 3:51:28

1 线程池使用场景?

java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。

加快请求响应(响应时间优先)
比如用户在饿了么上查看某商家外卖,需要聚合商品库存、店家、价格、红包优惠等等信息返回给用户,接口逻辑涉及到聚合、级联等查询,从这个角度来看接口返回越快越好,那么就可以使用多线程方式,把聚合/级联查询等任务采用并行方式执行,从而缩短接口响应时间。这种场景下使用线程池的目的就是为了缩短响应时间,往往不去设置队列去缓冲并发的请求,而是会适当调高corePoolSize和maxPoolSize去尽可能的创造线程来执行任务。

加快处理大任务(吞吐量优先)
比如业务中台每10分钟就调用接口统计每个系统/项目的PV/UV等指标然后写入多个sheet页中返回,这种情况下往往也会使用多线程方式来并行统计。和"时间优先"场景不同,这种场景的关注点不在于尽可能快的返回,而是关注利用有限的资源尽可能的在单位时间内处理更多的任务,即吞吐量优先。这种场景下我们往往会设置队列来缓冲并发任务,并且设置合理的corePoolSize和maxPoolSize参数,这个时候如果设置了太大的corePoolSize和maxPoolSize可能还会因为线程上下文频繁切换降低任务处理速度,从而导致吞吐量降低。

以上两种使用场景和JVM里的ParallelScavenge和CMS垃圾收集器有较大的类比性,ParallelScavenge垃圾收集器关注点在于达到可观的吞吐量,而CMS垃圾收集器重点关注尽可能缩短GC停顿时间。

在这里插入图片描述

2 线程池的创建及重要参数

线程池可以自动创建也可以手动创建,自动创建体现在Executors工具类中,常见的可以创建newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool;手动创建体现在可以灵活设置线程池的各个参数,体现在代码中即ThreadPoolExecutor类构造器上各个实参的不同:

public static ExecutorService newFixedThreadPool(int var0) {return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());}public static ExecutorService newSingleThreadExecutor() {return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));}public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());}public static ScheduledExecutorService newScheduledThreadPool(int var0) {return new ScheduledThreadPoolExecutor(var0);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {……}

3 ThreadPoolExecutor中重要的几个参数详解

corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
unit:keepAliveTime的时间单位
workQueue:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy

4 线程池中的线程创建流程图:

在这里插入图片描述

举个栗子:现有一个线程池,corePoolSize=10,maxPoolSize=20,队列长度为100,那么当任务过来会先创建10个核心线程数,接下来进来的任务会进入到队列中直到队列满了,会创建额外的线程来执行任务(最多20个线程),这个时候如果再来任务就会执行拒绝策略。

5 workQueue队列

SynchronousQueue(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零
LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致内存占用过多或OOM
ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务

6 常见的几种自动创建线程池方式

自动创建线程池的几种方式都封装在Executors工具类中:
newFixedThreadPool:使用的构造方式为new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()),设置了corePoolSize=maxPoolSize,keepAliveTime=0(此时该参数没作用),无界队列,任务可以无限放入,当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常

newSingleThreadExector:使用的构造方式为new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0),基本同newFixedThreadPool,但是将线程数设置为了1,单线程,弊端和newFixedThreadPool一致

newCachedThreadPool:使用的构造方式为new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()),corePoolSize=0,maxPoolSize为很大的数,同步移交队列,也就是说不维护常驻线程(核心线程),每次来请求直接创建新线程来处理任务,也不使用队列缓冲,会自动回收多余线程,由于将maxPoolSize设置成Integer.MAX_VALUE,当请求很多时就可能创建过多的线程,导致资源耗尽OOM

newScheduledThreadPool:使用的构造方式为new ThreadPoolExecutor(var1, 2147483647, 0L, TimeUnit.NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()),支持定时周期性执行,注意一下使用的是延迟队列,弊端同newCachedThreadPool一致

所以根据上面分析我们可以看到,FixedThreadPool和SigleThreadExecutor中之所以用LinkedBlockingQueue无界队列,是因为设置了corePoolSize=maxPoolSize,线程数无法动态扩展,于是就设置了无界阻塞队列来应对不可知的任务量;而CachedThreadPool则使用的是SynchronousQueue同步移交队列,为什么使用这个队列呢?因为CachedThreadPool设置了corePoolSize=0,maxPoolSize=Integer.MAX_VALUE,来一个任务就创建一个线程来执行任务,用不到队列来存储任务;SchduledThreadPool用的是延迟队列DelayedWorkQueue。在实际项目开发中也是推荐使用手动创建线程池的方式,而不用默认方式,关于这点在《阿里巴巴开发规范》中是这样描述的:

在这里插入图片描述

7 handler拒绝策略

AbortPolicy:中断抛出异常
DiscardPolicy:默默丢弃任务,不进行任何通知
DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

8 关闭线程池

shutdownNow():立即关闭线程池(暴力),正在执行中的及队列中的任务会被中断,同时该方法会返回被中断的队列中的任务列表
shutdown():平滑关闭线程池,正在执行中的及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略
isTerminated():当正在执行的任务及对列中的任务全部都执行(清空)完就会返回true

9 线程池实现线程复用的原理

什么是线程复用?
在线程池中,通过同一个线程去执行不同的任务,这就是线程复用。
假设现在有 100 个任务,我们创建一个固定线程的线程池(FixedThreadPool),核心线程数和最大线程数都是 3,那么当这个 100 个任务执行完,都只会使用三个线程。
示例:

public class FixedThreadPoolDemo {static ExecutorService executorService = Executors.newFixedThreadPool(3);public static void main(String[] args) {for (int i = 0; i < 100; i++) {executorService.execute(() -> {System.out.println(Thread.currentThread().getName() + "-> 执行");});}// 关闭线程池executorService.shutdown();}}执行结果:
pool-1-thread-1-> 执行
pool-1-thread-2-> 执行
pool-1-thread-3-> 执行
pool-1-thread-1-> 执行
pool-1-thread-3-> 执行
pool-1-thread-2-> 执行
pool-1-thread-3-> 执行
pool-1-thread-1-> 执行

线程复用的原理
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式将只使用固定的线程就将所有任务的 run 方法串联起来

线程池执行流程
这部分内容在 Java 线程池的各个参数的含义 讨论过,这里我们再复习一次,再从中去了解线程复用。
流程图

在这里插入图片描述

线程创建的流程
当任务提交之后,线程池首先会检查当前线程数,如果当前的线程数小于核心线程数(corePoolSize),比如最开始创建的时候线程数为 0,则新建线程并执行任务。
当提交的任务不断增加,创建的线程数等于核心线程数(corePoolSize),新增的任务会被添加到 workQueue 任务队列中,等待核心线程执行完当前任务后,重新从 workQueue 中获取任务执行。
假设任务非常多,达到了 workQueue 的最大容量,但是当前线程数小于最大线程数(maximumPoolSize),线程池会在核心线程数(corePoolSize)的基础上继续创建线程来执行任务。
假设任务继续增加,线程池的线程数达到最大线程数(maximumPoolSize),如果任务继续增加,这个时候线程池就会采用拒绝策略来拒绝这些任务。
在任务不断增加的过程中,线程池会逐一进行以下 4 个方面的判断

核心线程数(corePoolSize)
任务队列(workQueue)
最大线程数(maximumPoolSize)
拒绝策略

10 ThreadPoolExecutor#execute 源码分析

java.util.concurrent.ThreadPoolExecutor#execute
public void execute(Runnable command) {// 如果传入的Runnable的空,就抛出异常if (command == null)throw new NullPointerException();int c = ctl.get();// 线程池中的线程比核心线程数少 if (workerCountOf(c) < corePoolSize) {// 新建一个核心线程执行任务if (addWorker(command, true))return;c = ctl.get();}// 核心线程已满,但是任务队列未满,添加到队列中if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了if (! isRunning(recheck) && remove(command))// 如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务reject(command);else if (workerCountOf(recheck) == 0)// 如果之前的线程已经被销毁完,新建一个非核心线程addWorker(null, false);}// 核心线程池已满,队列已满,尝试创建一个非核心新的线程else if (!addWorker(command, false))// 如果创建新线程失败,说明线程池关闭或者线程池满了,拒绝任务reject(command);}

逐行分析
//如果传入的Runnable的空,就抛出异常 if (command == null) throw new NullPointerException();
execute 方法中通过 if 语句判断 command ,也就是 Runnable 任务是否等于 null,如果为 null 就抛出异常。

if (workerCountOf© < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); }
判断当前线程数是否小于核心线程数,如果小于核心线程数就调用 addWorker() 方法增加一个 Worker,这里的 Worker 就可以理解为一个线程。
addWorker 方法的主要作用是在线程池中创建一个线程并执行传入的任务,如果返回 true 代表添加成功,如果返回 false 代表添加失败。
第一个参数表示传入的任务
第二个参数是个布尔值,如果布尔值传入 true 代表增加线程时判断当前线程是否少于 corePoolSize,小于则增加新线程(核心线程),大于等于则不增加;同理,如果传入 false 代表增加线程时判断当前线程是否少于 maximumPoolSize,小于则增加新线程(非核心线程),大于等于则不增加,所以这里的布尔值的含义是以核心线程数为界限还是以最大线程数为界限进行是否新增非核心线程的判断

这一段判断相关源码如下

private boolean addWorker(Runnable firstTask, boolean core) {     ...int wc = workerCountOf(c);//当前工作线程数//判断当前工作线程数>=最大线程数 或者 >=核心线程数(当core = true)if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;...

最核心的就是 core ? corePoolSize : maximumPoolSize 这个
三目运算。

// 核心线程已满,但是任务队列未满,添加到队列中if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了if (! isRunning(recheck) && remove(command))// 如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务reject(command);else if (workerCountOf(recheck) == 0)// 如果之前的线程已经被销毁完,新建一个非核心线程addWorker(null, false);}

如果代码执行到这里,说明当前线程数大于或等于核心线程数或者 addWorker 失败了,那么就需要通过
if (isRunning© && workQueue.offer(command)) 检查线程池状态是否为 Running,如果线程池状态是 Running 就通过 workQueue.offer(command) 将任务放入任务队列中,
任务成功添加到队列以后,再次检查线程池状态,如果线程池不处于 Running 状态,说明线程池被关闭,那么就移除刚刚添加到任务队列中的任务,并执行拒绝策略,代码如下:
线程复用源码分析
java.util.concurrent.ThreadPoolExecutor#runWorker
省略掉部分和复用无关的代码之后,代码如下

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // 释放锁 设置work的state=0 允许中断boolean completedAbruptly = true;try {//一直执行 如果task不为空 或者 从队列中获取的task不为空while (task != null || (task = getTask()) != null) {task.run();//执行task中的run方法}}completedAbruptly = false;} finally {//1.将 worker 从数组 workers 里删除掉//2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组 workersprocessWorkerExit(w, completedAbruptly);}}

可以看到,实现线程复用的逻辑主要在一个不停循环的 while 循环体中。
通过获取 Worker 的 firstTask 或者通过 getTask 方法从 workQueue 中获取待执行的任务
直接通过 task.run() 来执行具体的任务(而不是新建线程)
在这里,我们找到了线程复用最终的实现,通过取 Worker 的 firstTask 或者 getTask 方法从 workQueue 中取出了新任务,并直接调用 Runnable 的 run 方法来执行任务,也就是如之前所说的,每个线程都始终在一个大循环中,反复获取任务,然后执行任务,从而实现了线程的复用。

11 手动创建线程池(推荐)

那么上面说了使用Executors工具类创建的线程池有隐患,那如何使用才能避免这个隐患呢?对症下药,建立自己的线程工厂类,灵活设置关键参数:
//这里默认拒绝策略为AbortPolicy

private static ExecutorService executor = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new ArrayBlockingQueue(10));
使用guava包中的ThreadFactoryBuilder工厂类来构造线程池:
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().build();private static ExecutorService executorService = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), threadFactory, new ThreadPoolExecutor.AbortPolicy());
通过guava的ThreadFactory工厂类还可以指定线程组名称,这对于后期定位错误时也是很有帮助的
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-d%").build();

12 Springboot中使用线程池

springboot可以说是非常流行了,下面说说如何在springboot中优雅的使用线程池

/*** @ClassName ThreadPoolConfig* @Description 配置类中构建线程池实例,方便调用* @Author simonsfan* @Date 2018/12/20* Version  1.0*/
@Configuration
public class ThreadPoolConfig {@Bean(value = "threadPoolInstance")public ExecutorService createThreadPoolInstance() {//通过guava类库的ThreadFactoryBuilder来实现线程工厂类并设置线程名称ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build();ExecutorService threadPool = new ThreadPoolExecutor(10, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());return threadPool;}//通过name=threadPoolInstance引用线程池实例@Resource(name = "threadPoolInstance")private ExecutorService executorService;@Overridepublic void spikeConsumer() {//TODOexecutorService.execute(new Runnable() {@Overridepublic void run() {//TODO}});}

13 其它相关

在ThreadPoolExecutor类中有两个比较重要的方法引起了我们的注意:beforeExecute和afterExecute

protected void beforeExecute(Thread var1, Runnable var2) {}protected void afterExecute(Runnable var1, Throwable var2) {}

这两个方法是protected修饰的,很显然是留给开发人员去重写方法体实现自己的业务逻辑,非常适合做钩子函数,在任务run方法的前后增加业务逻辑,比如添加日志、统计等。这个和我们springmvc中拦截器的preHandle和afterCompletion方法很类似,都是对方法进行环绕,类似于spring的AOP,参考下图:

在这里插入图片描述

14 Callable和Runnable

Runnable和Callable都可以理解为任务,里面封装这任务的具体逻辑,用于提交给线程池执行,区别在于Runnable任务执行没有返回值,且Runnable任务逻辑中不能通过throws抛出cheched异常(但是可以try catch),而Callable可以获取到任务的执行结果返回值且抛出checked异常。

@FunctionalInterface
public interface Runnable {public abstract void run();
}@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}

15 Future和FutureTask

Future接口用来表示执行异步任务的结果存储器,当一个任务的执行时间过长就可以采用这种方式:把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果。Future的主要方法包括:

get()方法:返回任务的执行结果,若任务还未执行完,则会一直阻塞直到完成为止,如果执行过程中发生异常,则抛出异常,但是主线程是感知不到并且不受影响的,除非调用get()方法进行获取结果则会抛出ExecutionException异常;
get(long timeout, TimeUnit unit):在指定时间内返回任务的执行结果,超时未返回会抛出TimeoutException,这个时候需要显式的取消任务;
cancel(boolean mayInterruptIfRunning):取消任务,boolean类型入参表示如果任务正在运行中是否强制中断;
isDone():判断任务是否执行完毕,执行完毕不代表任务一定成功执行,比如任务执行失但也执行完毕、任务被中断了也执行完毕都会返回true,它仅仅表示一种状态说后面任务不会再执行了;
isCancelled():判断任务是否被取消;
下面来实际演示Future和FutureTask的用法:

public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(10);Future<Integer> future = executorService.submit(new Task());Integer integer = future.get();System.out.println(integer);executorService.shutdown();}static class Task implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println("子线程开始计算");int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}return sum;}
}

16 实现优先使用运行线程及调整线程数大小的线程池(线程池的优化)

当前在JDK中默认使用的线程池 ThreadPoolExecutor,在具体使用场景中,有以下几个缺点
core线程一般不会timeOut
新任务提交时,如果工作线程数小于 coreSize,会自动先创建线程,即使当前工作线程已经空闲,这样会造成空闲线程浪费
设置的maxSize参数只有在队列满之后,才会生效,而默认情况下容器队列会很大(比如1000)
如一个coreSize为10,maxSize为100,队列长度为1000的线程池,在运行一段时间之后的效果会是以下2个效果:

系统空闲时,线程池中始终保持10个线程不变,有一部分线程在执行任务,另一部分线程一直wait中(即使设置allowCoreThreadTimeOut)
系统繁忙时,线程池中线程仍然为10个,但队列中有还没有执行的任务(不超过1000),存在任务堆积现象

空闲线程优先
空闲线程优先在基本逻辑中,即如果线程数小于coreSize,但如果有空闲线程,就取消创建线程的逻辑. 在有空闲线程的情况下,直接将任务放入队列中,即达到任务执行的目的。
这里的逻辑即是直接调整默认的ThreadPoolExecutor逻辑,通过重载 execute(Runnable) 方法达到效果. 具体代码如下所示:

public void execute(Runnable command) {//此处优先处理有活跃线程的情况,避免在<coreSize时,直接创建线程if(getActiveCount() < getPoolSize()) {if(pool1.offer(command)) {return;}}super.execute(command);
}

17 coreSize满了优先创建线程

从之前的逻辑来看,如果放入队列失败,则尝试创建新线程。在这个时候,相应的coreSize肯定已经满了。那么,只需要处理一下逻辑,将其offer调整为false,即可以实现相应的目的。
这里的逻辑,即是重新定义一个BlockingDeque,重载相应的offer方法,相应的参考如下:

public boolean offer(Runnable o) {//这里的parent为ThreadPoolExecutor的引用int poolSize = parent.getPoolSize();int maxPoolSize = parent.getMaximumPoolSize();//还没到最大值,先创建线程if(poolSize < maxPoolSize) {return false;}//默认逻辑return super.offer(o);
}

即判定当前线程池中线程数如果小于最大线程数,即直接返回false,达到放入队列失败的效果。
总结
按照以上的调整,只需要通过继承自默认的ThreadPoolExecutor和默认的BlockingQueue(如LinkedBlockingDeque),重载2个主要的方法 ThreadPoolExecutor#execute 和 LinkedBlockingDeque#offer 即达到调整的目的。


http://chatgpt.dhexx.cn/article/leRX3TvG.shtml

相关文章

Netty 线程池

Netty的线程池有什么样的特性 Java 原生线程池 Java 原生的线程池主要有三种&#xff1a;ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。 ThreadPoolExecutor 是最古老的类&#xff0c;我们通常说的线程池&#xff0c;也是指这个类。 ScheduledThreadPoo…

Linux —— 线程池

目录 一、什么是线程池 二、线程池的优点 三、线程池的应用 四、实现一个简单的线程池 五、单例模式 1. 饿汉实现方式 2. 懒汉实现方式 3. 单例模式实现线程池&#xff08;懒汉方式&#xff09; 六、其他常见的各种锁 一、什么是线程池 线程池是线程的一种使用模式。在…

线程池的实现原理

系统学习性&#xff0c;移步 IT-BLOG 线程池做的工作主要是控制运行的线程数量&#xff0c;处理过程中将任务放入队列&#xff0c;然后在线程创建后启动这些任务&#xff0c;如果线程数超过了最大数量超出数量的线程排队等候&#xff0c;等其他线程执行完毕&#xff0c;再从队列…

java——线程池

一、线程池 线程池可以看做是线程的集合。它的工作主要是控制运行的线程的数量&#xff0c;处理过程中将任务放入队列&#xff0c;然后在线程创建后 启动这些任务&#xff0c;如果线程数量超过了最大数量超出数量的线程排队等候&#xff0c;等其它线程执行完毕&#xff0c; 再…

java线程池(详解)

线程池介绍 线程池&#xff08;thread pool&#xff09;&#xff1a;一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;对线程统一管理。 线程池就是存放线程的池子&#xff0c;池子里存放了很多可以复…

Java线程池详解

本文包含知识点 线程池的使用场景分析线程池的创建及重要参数线程池实现线程复用的原理springboot中使用线程池Callabel与Runnable任务在基于spring体系的业务中正确地关闭线程池实现优先使用运行线程及调整线程数大小的线程池(线程池的优化)在java web项目中慎用Executors以及…

C++线程池

1.基础概念 线程池&#xff1a;一种线程的使用模式&#xff0c;线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性。而线程池维护着多个线程&#xff0c;等待监督管理者分配可并行执行的任务。这样避免了在短时间内创建和销毁线程的代价。线程池不仅能够内核的充分…

线程池详解

成功不是将来才有的&#xff0c;而是从决定去做的那一刻起&#xff0c;持续累积而成。 目录 背景 线程池介绍 线程池使用 Executors 线程池如何关闭&#xff1f; 面试题 总结 背景 下面是一段创建线程并运行的代码: for (int i 0; i < 100; i) {new Thread(() -&…

线程池(通俗易懂)

目录 一、什么是线程池 二、创建线程池的方式 三、线程池的七大参数 四、四种拒绝策略 1.AbortPolicy() 2.CallerRunsPolicy() 3.DiscardPolicy() 4.DiscardOldestPolicy() 五、自定义一个线程池 1.场景描述 2.代码实现 一、什么是线程池 线程池其实就是一种多线程处理…

线程池研发学习笔记

线程池研发 线程池 线程池基础 概念介绍 1:什么是线程池 可以直接叙述,也可以对比连接池介绍 线程池其实就是一种多线程处理形式&#xff0c;处理过程中可以将任务添加到队列中&#xff0c;然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是…

线程池是什么?线程池(ThreadPoolExecutor)使用详解

点一点&#xff0c;了解更多https://www.csdn.net/ 本篇文章将详细讲解什么是线程池&#xff0c;线程池的参数介绍&#xff0c;线程池的工作流程&#xff0c;使用Executors创建常见的线程池~~~ 目录 点一点&#xff0c;了解更多 文章目录 一、线程池的概念 1.1线程池的目的…

写给小白看的线程池,还有10道面试题

如何搞定20k的面试小抄 为什么要用线程池呢&#xff1f; 下面是一段创建线程并运行的代码: for (int i 0; i < 100; i) {new Thread(() -> {System.out.println("run thread->" Thread.currentThread().getName());userService.updateUser(....);}).start…

线程池详解(通俗易懂超级好)

目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础线程池使用线程池综合案例学员练习线程池总结 概念介绍 什么是线程池为什么使用线程池线程池有哪些优势 什么…

Java 多线程:彻底搞懂线程池

熟悉 Java 多线程编程的同学都知道&#xff0c;当我们线程创建过多时&#xff0c;容易引发内存溢出&#xff0c;因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列&#xff08;workQueue&#x…

GridView概述

一、使用GridView以表格形式显示多张图片 GridView用于在界面上按行、列分布的方式来显示多个组件 二、使用GridView 1、java代码 import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterV…

Master-Detail GridView

梦幻版Master-Detail GridView(黄忠成) 2007-12-26 09:34 前面的Master-Detail GridView控件應用&#xff0c;相信你已在市面上的書、或網路上見過&#xff0c;但此節中的GridView控件應用包你沒看過&#xff0c;但一定想過&#xff01;請見圖4-8-63。 圖4-8-63 圖 4-8-64 你一…

GridView DataGrid

ASP.NET 2.0提供了功能强大的数据绑定控件GridView、在使用中&#xff0c;一些属性和方法经常会与ASP.NET 1.1中的DataGrid混淆(VS2005中依然可以使用DataGrid&#xff0c;手动添加到工具箱或HTML状态输入代码)&#xff0c;下面我们分别用GridView和DataGrid实现其数据绑定、编…

GridView详讲

GridView是ASP.NET界面开发中的一个重要的控件&#xff0c;对GridView使用的熟练程度直接影响软件开发的进度及功能的实现。(车延禄) GridView的主要新特性&#xff1a; 1.与DataSource控件结合实现了显示与数据操作的分离&#xff0c;大大减化了代码的编写量; 2.实现"双向…

GridView详述

GridView无代码分页排序GridView选中&#xff0c;编辑&#xff0c;取消&#xff0c;删除GridView正反双向排序GridView和下拉菜单DropDownList结合GridView和CheckBox结合鼠标移到GridView某一行时改变该行的背景色方法一鼠标移到GridView某一行时改变该行的背景色方法二GridVi…

GridView、ListView、Adapter、Map、HashMap

1.ListView自定义适配器adapter 注&#xff1a;Android适配器是数据和视图之间的桥梁&#xff0c;以便于数据在View上显示。适配器就像显示器&#xff0c;把复杂的东西按人可以接受的方式来展现。 &#xff08;1&#xff09;首先将适配器的View视图表现出来&#xff0c;使用L…