见招拆招:老油条教你如何化解大厂面试官的线程池夺命连环炮!!!

网友投稿 648 2022-05-30

什么是线程池?

使用线程池的好处

线程池的核心参数

线程池的处理流程

线程池的创建方式有哪些?

常用线程池及它们的使用场景

线程池被创建后里面有线程吗?

你知道有什么方法对线程池进行预热吗?

线程池的状态有哪些?

线程池的拒绝策略有那些?

线程池的线程数到底怎么配置?

execute 和 submit的区别

尾言

什么是线程池?

使用线程池的好处

线程池的核心参数

线程池的处理流程

线程池的创建方式有哪些?

常用线程池及它们的使用场景

线程池被创建后里面有线程吗?

你知道有什么方法对线程池进行预热吗?

线程池的状态有哪些?

线程池的拒绝策略有那些?

线程池的线程数到底怎么配置?

execute 和 submit的区别

尾言

什么是线程池?

线程池可以理解为一个具有多个线程的线程集合.

使用线程池的好处

降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的核心参数

corePoolSize 核心线程数,没达到核心线程数时,会创建新的线程。当达到核心线程数时,任务会进去队列

maximumPoolSize 最大线程数,可以为Integer.MAX_VALUE 21亿。当达到核心线程数且队列满了的时候,会去创建额外的线程来执行任务,最多不超过最大线程数

keepAliveTime 存活时间,当任务处理完成,额外的线程存活一段时间后,会自行销毁。空闲等待时间(该参数默认对核心线程无效,当allowCoreThreadTimeOut手动设置为true时,核心线程超过存活时间后才会被销毁)

TimeUnit 空闲等待时间的单位

BlockingQueue :任务进来,如果核心线程数满了,则任务进入队列中等待。

ThreadFactory 线程创建工厂

RejectExecutionHandler拒绝策略,当最大线程数满了并且队列也满了的时候,如果再有任务进来就会启用拒绝策略。

参考源码

/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters and default thread factory and rejected execution handler. * It may be more convenient to use one of the {@link Executors} factory * methods instead of this general purpose constructor. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @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. * @throws IllegalArgumentException if one of the following holds:
* {@code corePoolSize < 0}
* {@code keepAliveTime < 0}
* {@code maximumPoolSize <= 0}
* {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }

线程池的处理流程

线程池的创建方式有哪些?

通过Executors工具类创建指定线程池

通过 new ThreadPoolExecutor() 自定义线程池,传入指定参数

常用线程池及它们的使用场景

newFixedThreadPool():固定线程数的线程池

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }

线程池特点:

核心线程数和最大线程数大小一样

没有所谓的非空闲时间,即keepAliveTime为0

阻塞队列为无界队列LinkedBlockingQueue

缺点

如果某任务执行时间过长,而导致大量任务堆积在阻塞队列中,或者说在某一时刻大量任务进来则会导致机器内存使用不断飙升,最终导致OOM

使用场景

newFixedThreadPool 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。

newCachedThreadPool()

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); }

线程池特点:

核心线程数为0

最大线程数为Integer.MAX_VALUE

阻塞队列是SynchronousQueue

非核心线程空闲存活时间为60秒

缺点

如果任务的提交速度大于线程处理任务的速度,那么就会不断地创建新线程极端情况下会耗尽CPU和内存资源

CachedThreadPool允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。

任务队列采用的是SynchronousQueue,这个队列是无法插入任务的,一有任务立即执行

使用场景

适用于并发执行大量短期的小任务。

newSingleThreadExecutor()

public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); }

线程池特点

核心线程数为1

最大线程数也为1

阻塞队列是LinkedBlockingQueue

keepAliveTime为0

缺点

LinkedBlockingQueue 为无界队列,可能会导致OOM

使用场景

适用于串行执行任务的场景,一个任务一个任务地执行。

newScheduledThreadPool()

public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }

线程池特点

最大线程数为Integer.MAX_VALUE

阻塞队列是DelayedWorkQueue

keepAliveTime为0

scheduleAtFixedRate() :按某种速率周期执行

scheduleWithFixedDelay():在某个延迟后执行

使用场景

周期性执行任务的场景,需要限制线程数量的场景

线程池被创建后里面有线程吗?

线程池被创建后如果没有任务过来,是不会有线程的。

你知道有什么方法对线程池进行预热吗?

==线程预热可以使用以下两个方法==

1.只启动一个线程预热

2.全部启动预热

线程池的状态有哪些?

参考源码

//记录线程池的状态,已经线程池中线程的个数,初始化状态为 Running private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //线程池的五种状态 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;

1、RUNNING

状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。

状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

2、ShutDown

状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。

状态切换:调用线程池的shutdown() 时,线程池由RUNNING -> SHUTDOWN。

3、STOP

状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。

状态切换:调用线程池的shutdownNow() 时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、tidying

状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。

当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。

状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、 TERMINATED(terminated)

状态说明:线程池彻底终止,就变成TERMINATED状态。

状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

线程池的拒绝策略有那些?

AbortPolicy(默认),直接抛出一个类型为 RejectedExecutionException 的 RuntimeException异常阻止系统的正常运行。

DiscardPolicy:直接丢弃任务,不给予任何处理也不抛出异常。如果允许任务丢失的话,这是最好的方案。

见招拆招:老油条教你如何化解大厂面试官的线程池夺命连环炮!!!

DiscardOldestPolicy,抛弃队列中等待时间最长的任务,然后把当前任务加入队列中尝试再次提交任务。

CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

线程池的线程数到底怎么配置?

判断当前任务是CPU 密集型还是 IO 密集型

公式

CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。 一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。

I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

execute 和 submit的区别

1. 方法来源不同

execut()是在线程池的顶级接口Executor中定义的,而且只有这一个接口,可见这个方法的重要性。

public interface Executor { void execute(Runnable command); }

在ThreadPoolExecutor类中有它的具体实现。

submit()是在ExecutorService接口中定义的,并定义了三种重载方式,具体可以查看JDK文档

Future submit(Callable task); Future submit(Runnable task); Future submit(Runnable task, T result);

2. 接受参数不同

execute()方法只能接收实现Runnable接口类型的任务

submit()方法则既可以接收Runnable类型的任务,也可以接收Callable类型的任务。

3. 返回值不同

execute()的返回值是void,线程提交后不能得到线程的返回值。

submit()的返回值是Future,通过Future的get()方法可以获取到线程执行的返回值,get()方法是同步的,执行get()方法时,如果线程还没执行完,会同步等待,直到线程执行完成。

虽然submit()方法可以提交Runnable类型的参数,但执行Future方法的get()时,线程执行完会返回null,不会有实际的返回值,这是因为Runable本来就没有返回值

4. 对于异常处理不同

execute在执行任务时,如果遇到异常会直接抛出,

而submit不会直接抛出,只有在调用Future的get方法获取返回值时,才会抛出异常。

尾言

我是 Code皮皮虾,未来的日子里会不断更新出对大家有益的博文,期待大家的关注!!!

创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以==和关注我哦==,感谢支持,我们下次再见~~~

==分享大纲==

大厂面试题专栏

Java从入门到入坟学习路线目录索引

开源爬虫实例教程目录索引

更多精彩内容分享,请点击 Hello World (●’◡’●)

任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:什么是事务数据库?丨【绽放吧!数据库】
下一篇:excel表格统计个数的方法步骤图
相关文章

 发表评论

暂时没有评论,来抢沙发吧~