java并发编程---线程池详解

一、线程池创建

创建线程池我们一般会采用ThreadPoolExecutor的方式去根据自己实际业务需求自定义线程池,至于为什么摒弃Executors工具类的创建方式可以看下阿里开发手册给出的解释(上面第二条我感觉也很有必要就多截了一点):
在这里插入图片描述
参数解释:

ThreadPoolExecutor(
int corePoolSize,      // 线程池长期维持的线程数,即使线程处于闲置状态也不会回收。只有等于1时任务执行完后,没有任务请求进入时才会销毁
int maximumPoolSize,  // 线程池维护线程数上限
long keepAliveTime,   // 线程池中线程空闲时间阙值,达到时线程会被销毁,只到剩下corePoolSize个线程为止
TimeUnit unit,       // NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS ,千分之一微妙、千分之一毫秒、千分之一秒、秒
BlockingQueue<Runnable> workQueue,  // 当请求线程数大于maximumPoolSize时,线程进入BlockingQueue阻塞队列的类型
ThreadFactory threadFactory,        // 用来生产一组相同任务的线程。主要用于设置生成的线程名词前缀、是否为守护线程以及优先级等
RejectedExecutionHandler handler)   // 拒绝策略,

参数可选项及详解:

  • corePoolSize :
When a new task is submitted in method {@link #execute(Runnable)},
and fewer than corePoolSize threads are running, a new thread is
created to handle the request, even if other worker threads are
idle.  If there are more than corePoolSize but less than
maximumPoolSize threads running, a new thread will be created only
if the queue is full.  By setting corePoolSize and maximumPoolSize
the same, you create a fixed-size thread pool. By setting
maximumPoolSize to an essentially unbounded value such as {@code
Integer.MAX_VALUE}, you allow the pool to accommodate an arbitrary
number of concurrent tasks. Most typically, core and maximum pool
sizes are set only upon construction, but they may also be changed
dynamically using {@link #setCorePoolSize} and {@link
#setMaximumPoolSize}. </dd>

类开头这段注释中为我们解释了,当一个新任务过来时假如此时线程池中的线程数有空闲且没有达到coreSize但是并不会使用空闲的线程而是会重新创建一个。如果运行的线程数大于corePoolSize但小于maximumPoolSize,则仅在队列已满时才创建新线程。给我们解释了线程池一部分的运行流程。

 <dd>By default, even core threads are initially created and
 started only when new tasks arrive, but this can be overridden
 dynamically using method {@link #prestartCoreThread} or {@link
 #prestartAllCoreThreads}.  You probably want to prestart threads if
 you construct the pool with a non-empty queue. </dd>

这段注释给我们提供了prestartCoreThread}或prestartAllCoreThreads}两个方法来改变线程池初始化方式从而直接一次性创建好coreSize个线程。如果使用非空队列构造池,则可能要预启动线程。

  • maximumPoolSize:

最大池大小。请注意,实际最大值在内部受“容量”限制。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是:如果使用了无界的任务队列这个参数就没什么效果。很好理解当缓存队列可以无限添加等待线程时就永远不会出现界限启用新的线程。

  • keepAliveTime:
    当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

  • BlockingQueue:

<li> If fewer than corePoolSize threads are running, the Executor
always prefers adding a new thread
rather than queuing.</li>
<li> If corePoolSize or more threads are running, the Executor
always prefers queuing a request rather than adding a new
thread.</li>
<li> If a request cannot be queued, a new thread is created unless
this would exceed maximumPoolSize, in which case, the task will be
rejected.</li>

这三句也诠释了线程池一部分运行流程,当当前线程数小于coreSize并不会用到阻塞队列而是新建线程,当执行请求书大于当前线程就会启用阻塞队列。再继而当阻塞队列也满时就会创建新的线程达到max。再有更多请求来时就只能采用最后的拒绝策略。
在这里插入图片描述
注释中给出了比较清晰的解析与不同阻塞队列优缺点,简单来说synchronousQueue是一个没有容量的队列它并不会接受任务放入队列中而是会直接继续创建新线程知道达到max。使用这种队列就需要将max根据情况相对设置大一些。LinkedBlockingQueue是一个无界队列它会直接导致max失效,不管有多少任务过来一股脑往队列里塞。ArrayBlockingQueue貌似是三个兄弟中看起来最正常的。它是一个有界队列,有助于防止资源耗尽,但更难于调优和控制。队列大小和最大池大小可能会相互折衷:使用大队列和小池可最大程度地减少CPU使用率,OS资源和上下文切换开销,但可能导致人为降低吞吐量。如果任务频繁阻塞(例如,如果它们受I / O限制),则系统可能能够安排比您原先允许的线程更多的时间。使用小队列通常需要更大的池大小,这会使CPU繁忙,但可能会遇到无法接受的调度开销,这也会降低吞吐量。具体使用哪个就得根据实际情况斟酌。

  • threadFactory:

通常我们不会使用默认的线程工厂,开头阿里手册给出了解释我们可以通过实现ThreadFactory接口或者使用如guava包中的ThreadFactoryBuilder来创建自定义的线程工厂,方便我们到时候根据线程名字排查问题。

//        ThreadFactoryBuilder threadFactoryBuilder = new ThreadFactoryBuilder();
  • RejectedExecutionHandler :

AbortPolicy:默认,丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:丢弃任务,但是不抛出异常(不推荐)。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中。
CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行。

默认为我们提供了四种拒绝策略,也可以通过实现 RejectedExecutionHandler 接口来定制处理策略。对一些时效性要求不高的任务我们可以通过自定义拒绝策略保存到数据库进行削峰填谷。在空闲时再提出来执行或者采用其它一些自定义策略。

二、任务提交

  • execute():
    用于提交不需要返回值的任务,无法判断任务是否被线程执行成功

  • submit():
    用于提交需要返回值的任务,或者执行想随时可以取消的线程。该方法返回一个Future对象。Future对象的get()方法获取返回值。get(0方法会阻塞当前线程直至任务完成。get()方法中也可以设置时间参数如果到时间依然没有执行完成会立即返回。Future对象的cancel(true/false)取消任务,未开始或已完成返回false,参数表示是否中断执行中的线程。下面是一个简单的demo

 		 AtomicInteger atomicInteger = new AtomicInteger(1);

        threadPoolExecutor.execute(atomicInteger::incrementAndGet);

        Thread.sleep(2000);

        System.out.println(atomicInteger.get());

        Future<Integer> future = threadPoolExecutor.submit(atomicInteger::incrementAndGet);

        System.out.println(future.get());

三、线程池关闭

它们的原理是遍历线程池中的工作线程,然后逐个调用线程的intercept方法来中断线程,所以无法响应中断的任务可能永远无法终止。

  • shutdown() :将线程池状态设置为:shutdown然后中断所有没有正在执行任务的线程
  • shutdownNow() : 首先将线程池的状态设置为stop,然后尝试停止所有正在执行或暂停任务的线程并返回等待执行任务的列表

只要调用了这两个方法中的任意一个,isShutdown()方法返回都是true,当所有任务都已经关闭才表示线程池关闭成功。这个时候调用isTerminaed()方法会返回true

四、执行流程

关于执行流程根据不同阻塞队列等情况都会有所不同,相对来说还是很容易明白的。下面提供一种普遍的使用有界队列的情况:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值