前言

线程池是什么

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

举个例子:我是一个包工头,代表线程池,手底下有若干工人代表线程池中的线程。如果我没接到项目,那么工人就相当于线程池中的空闲线程,一但我接到了项目,我可以立刻让我手下的工人去工作,每个工人同一时间执行只执行一个工作任务,执行完了就去

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

队列是什么

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

举个例子,我又是一个包工头,一开始我只接了一个小项目,所以只有三个工作任务,但我手底下有四个工人,那么其中三人各领一个工作任务去执行就好了,剩下一个人就先休息。但突然我又接到了几个大项目,那么有现在有很多工作任务了,但手底下的工人不够啊。

那么我有两个选择:

(1)雇佣更多的工人

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

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

为什么要使用线程池

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

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

Java自带的线程池

Executor接口是Executor的父接口,基于生产者--消费者模式,提交任务的操作相当于生产者,执行任务的线程则相当于消费者,如果要在程序中实现一个生产者--消费者的设计,那么最简单的方式通常是使用Executor。

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

常用的使用方法是调用Executors中的静态方法来创建一个连接池。

(1)newFixedThreadPool

代码演示:

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

测试结果:

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

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

(2)newCachedThreadPool

测试代码:

 public class ThreadPoolTest {
public static void main(String[] args){
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 4; i++){
Runnable runnable = new Runnable() {
public void run() {
CountDownLatch countDownLatch = new CountDownLatch(1); //计数器,用于阻塞线程
System.out.println(Thread.currentThread().getName() + "正在执行");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executor.execute(runnable);
}
}
}

测试结果:

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

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

(3)newSingleThreadExecutor

测试代码:

 public class ThreadPoolTest {
public static void main(String[] args){
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 4; i++){
final int index = i;
Runnable runnable = new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行工作任务--- >" + index);
}
};
executor.execute(runnable);
}
}
}

测试结果:

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

测试代码:

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

测试结果:

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创建固定的线程就不合适,创建少了到时队列里会塞进太多的工作任务导致处理不及时,创建多了会导致工作任务少时有太多的线程处于空闲状态造成资源浪费。

所以还是需要根据实际情况使用适合的创建方式。

深入理解Java自带的线程池和缓冲队列的更多相关文章

  1. [转] 引用 Java自带的线程池ThreadPoolExecutor详细介绍说明和实例应用

    PS: Spring ThreadPoolTaskExecutor vs Java Executorservice cachedthreadpool 引用 [轰隆隆] 的 Java自带的线程池Thre ...

  2. Java并发编程:4种线程池和缓冲队列BlockingQueue

    一. 线程池简介 1. 线程池的概念: 线程池就是首先创建一些线程,它们的集合称为线程池.使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动 ...

  3. SpringBoot项目框架下ThreadPoolExecutor线程池+Queue缓冲队列实现高并发中进行下单业务

    主要是自己在项目中(中小型项目) 有支付下单业务(只是办理VIP,没有涉及到商品库存),目前用户量还没有上来,目前没有出现问题,但是想到如果用户量变大,下单并发量变大,可能会出现一系列的问题,趁着空闲 ...

  4. 一天十道Java面试题----第三天(对线程安全的理解------>线程池中阻塞队列的作用)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 21.对线程安全的理解 22.Thread和Runnable的区别 23.说说你对守护线程的理解 24.ThreadLoc ...

  5. (转)Java并发编程:线程池的使用

    背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...

  6. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  7. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  8. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

  9. Java并发编程:线程池的使用(转)

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

随机推荐

  1. Audio播放

    <audio controls="controls" id="warnAudio" hidden> <source src="~/m ...

  2. Java集合框架体系JCF

    Java 集合框架体系作为Java 中十分重要的一环, 在我们的日常开发中扮演者十分重要的角色, 那么什么是Java集合框架体系呢? 在Java语言中,Java语言的设计者对常用的数据结构和算法做了一 ...

  3. Mysql5.6二进制包安装方法

    1.Download MySQL Community Server 访问mysql官方网站转到下载页https://dev.mysql.com/downloads/mysql/5.6.html#dow ...

  4. Microsoft Visual Studio 2012 添加实体数据模型

     Microsoft Visual Studio 2012 添加实体数据模型 1.创建一个web项目 2.添加ADO实体数据模型,如下图: 3.选择 从数据库生成,然后下一步 4.新建连接,如下图: ...

  5. ubuntu如何安装chromium浏览器并设置成中文版

    在Ubuntu上使用APT安装Chromium有3种方法: 1.在Ubuntu软件中心输入chromium,然后在结果中选择安装即可. 2.在新立得软件包管理器中输入chromium,然后标记安装即可 ...

  6. git 多用户多仓库配置

    ssh全称是Secure Shell,即安全Shell,是一种可以进行安全远程登录的协议,在Linux中以OpenSSH为代表,Windows中则有Putty作为实现.ssh的会话建立阶段类似TCP协 ...

  7. 【html】使用img标签和背景图片之间的区别

    1.加载问题 背景图片会等到html结构加载完成才开始加载 img标签是网页结构的一部分,会在html结构加载的时候加载 在网页加载的过程中,背景图片会等到结构加载完成(网页的内容全部显示以后)才开始 ...

  8. 记一次在咸鱼上购买 MacBook Pro 的经历

    前言 以前一直用的是 windows 的,但是最近特别想买个 macOS 的.其实不是为了其他什么目的,只是涉及到开发 macOS更接近 linux 系统,一直没使用过所以就想尝试体验下,而且现在很多 ...

  9. Photoshop入门教程图解版

  10. 将root 当成arraylist放入数据sturts2 入门笔记

    刚启动idea 就报出错误 [-- ::,] Artifact -sturts2:war exploded: Error during artifact deployment. See server ...