深刻理解Java自带的线程池和缓冲队列

前言缓存

线程池是什么服务器

线程池的概念是初始化线程池时在池中建立空闲的线程,一但有工做任务,可直接使用线程池中的线程进行执行工做任务,任务执行完成后又返回线程池中成为空闲线程。使用线程池能够减小线程的建立和销毁,提升性能。多线程

举个例子:我是一个包工头,表明线程池,手底下有若干工人表明线程池中的线程。若是我没接到项目,那么工人就至关于线程池中的空闲线程,一但我接到了项目,我能够马上让我手下的工人去工做,每一个工人同一时间执行只执行一个工做任务,执行完了就去函数

执行另外一个工做任务,知道没有工做任务了,这时工人就能够休息了(原谅我让工人无休止的工做),也就是又变成了线程池中的空闲线程池。工具

队列是什么性能

队列做为一个缓冲的工具,当没有足够的线程去处理任务时,能够将任务放进队列中,以队列先进先出的特性来执行工做任务测试

举个例子,我又是一个包工头,一开始我只接了一个小项目,因此只有三个工做任务,但我手底下有四个工人,那么其中三人各领一个工做任务去执行就行了,剩下一我的就先休息。但忽然我又接到了几个大项目,那么有如今有不少工做任务了,但手底下的工人不够啊。spa

那么我有两个选择:操作系统

(1)雇佣更多的工人.net

(2)把工做任务记录下来,按先来后到的顺序执行

但雇佣更多等工人须要成本啊,对应到计算机就是资源的不足,因此我只能把工做任务先记录下来,这样就成了一个队列了。

为何要使用线程池

假设我又是一个包工头,我如今手底下没有工人了,但我接到了一个项目,有了工做任务要执行,那我确定要去找工人了,但招人成本是很高的,工做完成后还要给遣散费,这样算起来好像不值,因此我事先雇佣了固定的几个工人做为个人长期员工,有工做任务就干活,没有就休息,若是工做任务实在太

多,那我也能够再临时雇佣几个工人。一来二去工做效率高了,付出的成本也低了。Java自带的线程池的原理也是如此。

Java自带的线程池

Executor接口是Executor的父接口,基于生产者--消费者模式,提交任务的操做至关于生产者,执行任务的线程则至关于消费者,若是要在程序中实现一个生产者--消费者的设计,那么最简单的方式一般是使用Executor。

ExecutorService接口是对Executor接口的扩展,提供了对生命周期的支持,以及统计信息收集、应用程序管理机制和性能监视等机制。

经常使用的使用方法是调用Executors中的静态方法来建立一个链接池。

(1)newFixedThreadPool

代码演示:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ExecutorService executor = Executors.newFixedThreadPool(3);
 4         for (int i = 0; i < 4; i++){
 5             Runnable runnable = new Runnable() {
 6                 public void run() {
 7                     CountDownLatch countDownLatch = new CountDownLatch(1); //计数器,用于阻塞线程
 8                     System.out.println(Thread.currentThread().getName() + "正在执行");
 9                     try {
10                         countDownLatch.await();
11                     } catch (InterruptedException e) {
12                         e.printStackTrace();
13                     }
14                 }
15             };
16             executor.execute(runnable);
17         }
18     }
19 }

测试结果:

pool-1-thread-1正在执行
pool-1-thread-3正在执行
pool-1-thread-2正在执行

newFixedThreadPool将建立一个固定长度的线程池,每当提交一个任务时就会建立一个线程,直到达线程池的最大数量,这时线程池的规模再也不变化(若是某个线程因为发生了未预期的Exception而结束,那么线程池会补充一个新的线程)。上述代码中最大的线程数是3,但我提交了4个任务,并且每一个任务都阻塞住,因此前三个任务占用了线程池全部的线程,那么第四个任务永远也不会执行,所以该线程池配套使用的队列也是无界的。因此在使用该方法建立线程池时要根据实际状况看须要执行的任务是否占用过多时间,会不会影响后面任务的执行。

(2)newCachedThreadPool

测试代码:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ExecutorService executor = Executors.newCachedThreadPool();
 4         for (int i = 0; i < 4; i++){
 5             Runnable runnable = new Runnable() {
 6                 public void run() {
 7                     CountDownLatch countDownLatch = new CountDownLatch(1); //计数器,用于阻塞线程
 8                     System.out.println(Thread.currentThread().getName() + "正在执行");
 9                     try {
10                         countDownLatch.await();
11                     } catch (InterruptedException e) {
12                         e.printStackTrace();
13                     }
14                 }
15             };
16             executor.execute(runnable);
17         }
18     }
19 }

测试结果:

pool-1-thread-1正在执行
pool-1-thread-3正在执行
pool-1-thread-2正在执行
pool-1-thread-4正在执行

newCachedThreadPool将建立一个可缓存的线程池。若是线程池的当前规模超过了处理需求时,那么就会回收部分空闲的线程(根据空闲时间来回收),当需求增长时,此线程池又能够智能的添加新线程来处理任务。此线程池不会对线程池大小作限制,线程池大小彻底依赖于操做系统(或者说JVM)可以建立的最大线程大小。

(3)newSingleThreadExecutor

测试代码:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ExecutorService executor = Executors.newSingleThreadExecutor();
 4         for (int i = 0; i < 4; i++){
 5             final int index = i;
 6             Runnable runnable = new Runnable() {
 7                 public void run() {
 8                     System.out.println(Thread.currentThread().getName() + "正在执行工做任务--- >" + index);
 9                 }
10             };
11             executor.execute(runnable);
12         }
13     }
14 }

测试结果:

pool-1-thread-1正在执行工做任务--- >0
pool-1-thread-1正在执行工做任务--- >1
pool-1-thread-1正在执行工做任务--- >2
pool-1-thread-1正在执行工做任务--- >3

newSingleThreadExecutor是一个单线程的Executor,它建立单个工做者线程来执行任务,若是这个线程异常结束,会建立另外一个线程来代替。newSingleThreadExecutor能确保依照任务在队列中的顺序来串行执行。

(4)newScheduledThreadPool

测试代码:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
 4         for (int i = 0; i < 3; i++){
 5             final int index = i;
 6             Runnable runnable = new Runnable() {
 7                 public void run() {
 8                     System.out.println(Thread.currentThread().getName() + "延时1s后,每5s执行一次工做任务--- >" + index);
 9                 }
10             };
11             executor.scheduleAtFixedRate(runnable,1,5,TimeUnit.SECONDS);
12         }
13     }
14 }

测试结果:

pool-1-thread-1延时1s后,每5s执行一次工做任务--- >0
pool-1-thread-2延时1s后,每5s执行一次工做任务--- >1
pool-1-thread-3延时1s后,每5s执行一次工做任务--- >2
pool-1-thread-1延时1s后,每5s执行一次工做任务--- >0
pool-1-thread-3延时1s后,每5s执行一次工做任务--- >2
pool-1-thread-2延时1s后,每5s执行一次工做任务--- >1

newScheduledThreadPool建立了一个固定长度的线程池,并且以延迟或定时或周期的方式来执行任务,相似于Timer。可应用于重发机制。

以上四种建立线程池的方法其实都是调用如下这个方法,只是参数不同

corePoolSize  ---------------------> 核心线程数

maximumPoolSize ---------------> 最大线程数

keepAliveTime --------------------> 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间

unit -----------------------------------> 时间单位

workQueue ------------------------> 用于存储工做工人的队列

threadFactory ---------------------> 建立线程的工厂

handler ------------------------------> 因为超出线程范围和队列容量而使执行被阻塞时所使用的处理程序

经常使用的几种队列

(1)ArrayBlockingQueue:规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。

(2)LinkedBlockingQueue:大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。

(3)PriorityBlockingQueue:相似于LinkedBlockingQueue,可是其所含对象的排序不是FIFO,而是依据对象的天然顺序或者构造函数的Comparator决定。

(4)SynchronizedQueue:特殊的BlockingQueue,对其的操做必须是放和取交替完成。

排队策略(如下排队策略文字来自------->https://www.oschina.net/question/565065_86540

排队有三种通用策略:

直接提交。工做队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,若是不存在可用于当即运行任务的线程,则试图把任务加入队列将失败,所以会构造一个新的线程。此策略能够避免在处理可能具备内部依赖性的请求集时出现锁。直接提交一般要求无界 maximumPoolSizes 以免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略容许无界线程具备增加的可能性。

无界队列。使用无界队列(例如,不具备预约义容量的 LinkedBlockingQueue)将致使在全部 corePoolSize 线程都忙时新任务在队列中等待。这样,建立的线程就不会超过 corePoolSize。(所以,maximumPoolSize的值也就无效了。)当每一个任务彻底独立于其余任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略容许无界线程具备增加的可能性。

有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,可是可能较难调整和控制。队列大小和最大池大小可能须要相互折衷:使用大型队列和小型池能够最大限度地下降 CPU 使用率、操做系统资源和上下文切换开销,可是可能致使人工下降吞吐量。若是任务频繁阻塞(例如,若是它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列一般要求较大的池大小,CPU使用率较高,可是可能遇到不可接受的调度开销,这样也会下降吞吐量。

咱们选其中的LinkedBlockingQueue队列来解析

在上述Java自带的建立线程池的方法中,newFixedThreadPool使用的队列就是LinkedBlockingQueue

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

若是须要执行的工做任务少于核心线程数,那么直接使用线程池的空闲线程执行任务,若是任务不断增长,超过核心线程数,那么任务将被放进队列中,并且是没有限制的,线程池中的线程也不会增长。

其余线程池的工做队列也是根据排队的通用策略来进行工做,看客们能够本身分析。

总结:

没有建立链接池的方式,只有最适合的方式,使用Java自带的方式建立或者本身建立链接池都是可行的,但都要依照自身的业务状况选择合适的方式。

若是你的工做任务的数量在不一样时间差距很大,那么若是使用newFixedThreadPool建立固定的线程就不合适,建立少了到时队列里会塞进太多的工做任务致使处理不及时,建立多了会致使工做任务少时有太多的线程处于空闲状态形成资源浪费。

因此仍是须要根据实际状况使用适合的建立方式。

相关文章
相关标签/搜索