我是靠谱客的博主 内向紫菜,最近开发中收集的这篇文章主要介绍[转发]线程池的使用场景以及java中ThreadPoolExecutor类的讲解,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

转发:https://blog.csdn.net/qq_26012495/article/details/84325445

为什么要使用线程池?
对于java初学者来说,首先接触到的创建线程的方法就是new Thread,或者实现Runnable接口,重写run方法来实现多线程。虽然简单,但是一句话:谁来帮你管理线程???

若不使用线程池:

1、线程的创建销毁都要自己来完成

2、没有统一的管理,若每次请求都开启多个线程,无限制的请求袭来,可能造成资源耗尽

3、不够灵活

线程池的出现能让你双手游离于多线程之外专注于其他代码,帮你管理线程。所以如果是一个大型系统,建议不论何种场景,都直接使用线程池。

举个例子:你的面前有三台电脑,你可能同时用三台,也可能同时用两台或者一台。但是分配给你的电脑每次都是随机的。假设三台电脑编号123,第一次:【你要用1台分配到1号,然后开机->使用->关机】,第二次:【你要用1台分配到2号,然后开机->使用->关机】,以此类推。假设你的使用时间是10秒,而开机关机是20秒,然后这个过程要重复100次,每次都随机分配。那么无疑开机和关机的过程浪费的大量的时间和资源。

每次使用一个线程,都要经历三个步骤:创建线程->使用线程->销毁线程,类似上述使用场景,频繁的使用线程,频繁的开启和关闭,频繁的浪费时间和资源,额…如果这时候你面前的三台电脑虽然每次都随机分配,但是却从不关机,坐那就能用,就好了,yes!线程池就是完成了这样的操作!包括:数据库连接池也是同样的道理。线程池中每次创建的线程,如果执行完毕,不会立即进行销毁,而是处于等待状态,下一个任务来了以后无需开启直接使用,方便快捷!!!【线程池可以使已经开启的线程长期处于激活状态,节省创建和销毁线程的时间,实现线程复用!】

Java中的线程池
四大方法、7大参数、四种拒绝策略

java中,Executor是java.util.concurrent包下的一个线程池的鼻祖接口,只有一个抽象方法execute;ExecutorService仍然是一个接口,extends Executor,新增了一些接口例如submit;AbstractExecutorService是一个抽象类,implements ExecutorService;ThreadPoolExecutor是我们最常使用的线程池类,extends AbstractExecutorService;
请添加图片描述
Executors类提供了四种创建线程池的方法,源码如下,如果还不能满足你,那你只能自定义线程池了。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

package java.util.concurrent.ScheduledThreadPoolExecutor
extends ThreadPoolExecutor implements ScheduledExecutorService----->>>>

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

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

newFixedThreadPool:创建一个固定大小的线程池,提交一个任务就在线程池中创建一个线程,直到线程数量达到线程池最大限制。线程池一旦到最大就不会再改变,除非有已经开启的线程出现异常,再提交任务时会继续新建线程。这个创建线程池的方法是在实际项目中比较常用的,由于corePoolSize和maximumPoolSize两个参数是相同大小,所以到了线程池最大容量后,如果有任务完成让出占用线程,那么此线程就会一直处于等待状态,而不会消亡,直到下一个任务再次占用该线程。此方法的弊端是:使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无线增大,使服务器资源迅速耗尽。

newCachedThreadPool:创建一个可根据实际情况调整大小的线程池,线程数量不确定,只要有空闲线程空闲时间超过keepAliveTime,就会干掉,再来新任务,先使用空闲线程,若不够,再新建线程。线程池没有最大线程数量限制,所以当大量线程蜂拥而至,会造成资源耗尽。

newSingleThreadExecutor:创建一个容量为1的线程池,被提交任务按优先级依次执行。

newScheduledThreadPool:创建一个定长线程池,长度为输入参数自定义,支持定时周期任务执行,可根据时间需要在指定时间对线程进行调度。

可以看到 newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor均使用的ThreadPoolExecutor构造器,下面分析该构造方法的入参:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);走的是该类中另一个构造方法:如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
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;
}

7大参数:
corePoolSize:线程池中的核心线程数量,超过该数量的空闲线程空闲时间超过keepAliveTime则会被销毁,在该数量内的空闲线程则不会被销毁,在下一个任务来临之前一直处于等待状态。超过核心线程数的任务会进入队列等待
maximumPoolSize:线程池支持的最大线程数。当队列满了,会创建临时线程帮我们处理任务
keepAliveTime:超过corePoolSize的空闲线程的存活时间。临时线程执行完毕当前任务,会主动去队列中获取任务,如果经过keepAliveTime没有获取到,则销毁临时线程;核心线程默认不超时
unit:keeyAliveTime的单位。
workQueue:任务队列,当提交的任务超过线程池支持的最大线程数,则进入该队列排队等待。该队列的选择会对线程池的性能有重大影响。
threadFactory:线程工厂,用于创建线程,一般使用默认,也可自定义。
handler:当提交任务数量超过线程池容量,并且超过排队队列容量后的拒绝策略。

即:若corePoolSize=2,maximumPoolSize=5,maximumPoolSize=3

当来了2个线程:正好打开线程池的2个核心线程

当来了第3个线程,由于已经超过核心线程数,则先进入队列等待,此时队列容纳个数为1

当来了第6个线程,超过核心线程继续放入队列,但此时队列已满,于是迫不得已再次开辟线程池中新的线程

当来了第8个线程,线程池中所有线程全部打开,此时线程池已经达到最大线程数,且队列已满

当来了第9个线程,由于已经到了最大容载量,于是直接开始丢弃策略。

workQueue参数分析

workQueue是一个BlockingQueue接口的实现类,存放Runnable对象

阻塞队列:在任意时刻,不管多高的并发,永远只有一个线程能够进行入队或出队操作;

当队列已满,只能出队,所有入队操作等待,即阻塞;反之队列为空,只能入队。

在这里插入图片描述
一般来说,该队列的选择常用如下四种:

(1)SynchronousQueue:【直接提交的队列】,容量为0,每次插入都必须等待一个任务的删除操作,每次删除也要等待一个任务的插入操作。使用该队列,由于容量为0,其不会真实的保存任务,而是每次都将新任务提交给线程池,如果线程池满了,则直接执行拒绝策略,简单粗暴。

(2)ArrayBlockingQueue:【有界的任务队列,按照先进先出的顺序出队】,该队列的构造方法为public ArrayBlockingQueue(int capacity),初始化时必须设置其容量。当线程池中的实际线程数小于corePoolSize,则优先创建新的线程;如果大于corePoolSize,并且此时恰好没有空闲线程,则优先进入有界队列,队列满了以后,再从队列中出队任务创建线程,直到线程池中的线程数量达到maximumPoolSize。当大maximumPoolSize后,执行拒绝策略。

(3)LinkedBlockingQueue:【无界的任务队列,按照先进先出的顺序出队】,当线程池中的实际线程数小于corePoolSize,则优先创建新的线程;如果大于corePoolSize,并且此时恰好没有空闲线程,只要服务器资源足够多,就会无限制的进入入队操作,直到资源耗尽。所以使用了该任务队列的线程池最大支持线程数为corePoolSize,所以使用该队列的创建线程池的方法一般是corePoolSize和maximumPoolSize相等的newFixedThreadPool。

(4)PriorityBlockingQueue:【优先任务队列,按照任务的优先级出队】,带有任务执行优先级的队列,可以控制任务执行的先后顺序,在确保系统性能的同时,对质量也有了保证。

handler参数分析
在这里插入图片描述
丢弃策略是系统超负荷运行的最后补救措施,线程池满了,并且阻塞队列满了,丢弃策略有如下四种:

(1)ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException异常,丢弃任务,阻止系统正常工作。 【Executors类创建线程池的默认丢弃策略】
(2)ThreadPoolExecutor.DiscardPolicy:偷偷的丢弃任务,但是不抛出异常。
(3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃最老的任务(即队首马上要执行的任务),然后重新尝试执行任务(重复此过程)
(4)ThreadPoolExecutor.CallerRunsPolicy:只要线程池未关闭,直接去调用线程池的线程中处理该任务,可能引起性能的急剧下降。

execute方法与submit方法
前面说了,Executor是java.util.concurrent包下的一个线程池的鼻祖接口,只有一个抽象方法execute,该接口所有源码如下,execute方法入参为Runnable command。
public interface Executor {

/**
 * Executes the given command at some time in the future.  The command
 * may execute in a new thread, in a pooled thread, or in the calling
 * thread, at the discretion of the {@code Executor} implementation.
 *
 * @param command the runnable task
 * @throws RejectedExecutionException if this task cannot be
 * accepted for execution
 * @throws NullPointerException if command is null
 */
void execute(Runnable command);

}
该方法实际上是在ThreadPoolExecutor类中首次得到了实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个Runnable任务。所以我们在使用execute方法时,都要new Runnable。

submit()方法是在ExecutorService中声明的方法,如下:

/**
* Submits a Runnable task for execution and returns a Future
* representing that task. The Future’s {@code get} method will
* return {@code null} upon successful completion.
*
* @param task the task to submit
* @return a Future representing pending completion of the task
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if the task is null
*/
Future<?> submit(Runnable task);

在AbstractExecutorService类中首次进行了实现,在ThreadPoolExecutor中继承下来并且没有重写,如下:
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}

submit方法也是用来向线程池提交Runnable任务的,并且最终调用了execute方法,通过Future来获取任务执行结果。

execute和submit方法的不同点:

(1)execute没有返回值;而submit有返回值,方便返回执行结果。

(2)submit方便进行Exception处理,由于返回参数是Future,如果执行期间抛出了异常,可以使用Future.get()进行捕获。

合理的选择线程池大小
线程池设置过大,CPU、内存不够用,可能导致资源耗尽;

线程池设置过小,导致CPU空闲,降低吞吐量。

计算密集型

该任务需要大量CPU运算,没有阻塞,全速进行。

合适的线程池大小:CPU核心数+1

即使偶尔某个线程由于缺页中断或其他原因暂停,还有一个替补。

I/O密集型

I/O密集型,CPU不是一直在运转,还需要等待I/O时间等,因此线程数量应该设置的更大。必须估算出任务的等待时间与计算时间比值

合适的线程池大小:CPU核心数 * CPU利用率 *(1+任务等待时间 / 计算时间)

Runtime.getRuntime().availableProcessors()可以获取当前机器的CPU核数。

最后

以上就是内向紫菜为你收集整理的[转发]线程池的使用场景以及java中ThreadPoolExecutor类的讲解的全部内容,希望文章能够帮你解决[转发]线程池的使用场景以及java中ThreadPoolExecutor类的讲解所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部