深入理解java之线程池,Java并发编程——深入理解线程池

1.线程池

线程池是指管理一组同构工作线程的资源池。

1.1使用线程池的好处

bab28991a4f277f647a9ab3041eb3849.png

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

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

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

1.2 Executor框架

首先看下Executor源码,即Executor是一个核心的基础接口,其中只有execute方法

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);

}

1.3 Executor框架三大组成部分

任务(Runnable /Callable)

执行任务需要实现的 Runnable 接口 或 Callable接口。

任务的执行(Executor)

任务执行机制的核心接口 Executor ,以及继承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 这两个关键类实现了 ExecutorService 接口。

这里提了很多底层的类关系,但是,实际上我们需要更多关注的是 ThreadPoolExecutor 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的。

e5113de28fe245409c9aea8388a99a0b.png

异步计算的结果(Future)

Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。

当我们把 Runnable接口 或 Callable 接口 的实现类提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。调用 submit() 方法时会返回一个 FutureTask 对象

1.4 Executor框架的使用

dfa18ad97f3d9305abcfcf2ed4474146.png

主线程首先要创建实现 Runnable 或者 Callable 接口的任务对象。

把创建完成的实现 Runnable/Callable接口的 对象直接交给 ExecutorService 执行: ExecutorService.execute(Runnable command))或者也可以把 Runnable 对象或Callable 对象提交给 ExecutorService 执行(ExecutorService.submit(Runnable task)或 ExecutorService.submit(Callable task))。

如果执行 ExecutorService.submit(…),ExecutorService 将返回一个实现Future接口的对象(我们刚刚也提到过了执行 execute()方法和 submit()方法的区别,submit()会返回一个 `FutureTask 对象)。

主线程可以执行 FutureTask.get()方法来等待任务执行完成。

主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

1.5 ThreadPoolExecutor 类分析

线程池实现类 ThreadPoolExecutor 是 Executor 框架最核心的类。

话不多说,先看下ThreadPoolExecutor的核心构造函数:

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;

}

bab28991a4f277f647a9ab3041eb3849.pngThreadPoolExecutor 3 个最重要的参数

bab28991a4f277f647a9ab3041eb3849.png

079317e468d05e9b75929e18847f955a.png corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。

65cb18edef26fe473963d0b879d66fe7.png maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

aa99049c1e1fd2cd30de85e07650ae3a.png workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数:

keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;

unit : keepAliveTime 参数的时间单位。

threadFactory :executor 创建新线程的时候会用到。

handler :饱和策略。

bab28991a4f277f647a9ab3041eb3849.pngThreadPoolExecutor 饱和策略定义:如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略:

ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。这是默认的拒绝策略。

ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。

ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。

ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。

通过以下图片理解上述参数

898737992bfb1dffc228f3b7282a0d8c.png

1.6 线程池创建方法

1. 通过使用ThreadPoolExecutor的构造函数创建!

c21b9a33e0518db0bc22d073dfe9dc42.png

a8423c4608a3bafcb1a8410ce0667249.png

2. 通过使用Executors的静态工厂方法创建!

82af7b5f45022fdfc9f1b26a768e17b5.png

public static ExecutorService newFixedThreadPool(int nThreads) {

return new ThreadPoolExecutor(nThreads, nThreads,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue());

}

public static ExecutorService newSingleThreadExecutor() {

return new FinalizableDelegatedExecutorService

(new ThreadPoolExecutor(1, 1,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue()));

}

public static ExecutorService newCachedThreadPool() {

return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

60L, TimeUnit.SECONDS,

new SynchronousQueue());

}

public ScheduledThreadPoolExecutor(int corePoolSize,

ThreadFactory threadFactory) {

super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,

new DelayedWorkQueue(), threadFactory);

}

通过以上静态工厂方法我们可以发现不同的ThreadPoolExecutor有各自的缺点:

FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

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

ThreadPoolExecutor类执行流程

bab28991a4f277f647a9ab3041eb3849.png

fe18a8781fa9eb93e582145dbcff5ae2.png

本作品采用《CC 协议》,转载必须注明作者和本文链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值