阻塞队列模型和线程池

阻塞队列模型介绍

阻塞队列模型和线程池息息相关,因此本篇博客先介绍阻塞队列的相关知识。如下图所示:

首先我们来说,什么是Queue,然后在谈什么是BlockingQueue。

那么什么是Queue呢?一句话,就是一端进,另一端出,这样就形成了First In , First Out,即先进先出。而BlockingQueue只不过是在Queue的基础上进行了2个附加操作而已:如果Queue空,那么Out线程阻塞,如果Queue满,那么In线程阻塞。

理解了上面的Queue/BlockingQueue,那么就好理解Deque/BlockingDeque了。

Deque,就是双端队列,其实就是说在2端,都可以进行IN/OUT,当然如果我们只在同一端进行IN/OUT,那么自然形成了栈结构(先进后出)。

其次,我们先来看看java.util.concurrent.BlockingQueue的API列表:

其实从这里可以看出JAVA API的一个思路,往往一个操作提供多种选择:

如果队列是空的,那么消费线程该如何处理呢?

可以立刻抛出异常,也可以返回false/NULL,也可以过一段时间在TRY...

好了,到这里,我们只看到了接口API,如果要你来实现,你会怎么做呢?又会有哪些疑问呢?

思考:

是否应该提供一个存储,来放置队列中的元素呢?

这个存储应该多大呢?可以是什么形式呢?

队列中的元素是否存在优先级排序呢?

对队列中的元素进行操作时,是否应该有锁的控制呢?

一端IN,另一端OUT,这2个操作可以同时进行吗?

带着这些疑问,我们来对典型的BlockingQueue来进行分析。

ArrayBlockingQueue PK LinkedBlockingQueue

存储PK:

从名称上就可以知道,一个用的是数组,另一个用的是链表。看看源码验证下:

ArrayBlockingQueue:

LinkedBlockingQueue:

容量PK:

ArrayBlockingQueue可以通过构造方法指定容量:

而LinkedBlockingQueue如果在初始化时不指定容量,那么将是Integer.MAX_VALUE,这一点很重要,特别是在生产者的速度大于消费者的速度,由于此时无容量限制,将导致队列中的元素开始膨胀,那么将消耗掉大量系统资源。

锁PK:

在ArrayBlockingQueue中只有一个锁:

而在LinkedBlockingQueue中有2个锁:

其实到这里,我们已经可以大致猜测出,LinkedBlockingQueue对于take/put使用了分别的锁,从而比ArrayBlockingQueue在高并发下更具优势。

我们再来看看其他BlockingQueue:

DelayQueue:延迟队列,实际上是说,队列中的元素生效的话,有个时间差。

PriorityQueue:优先级队列,会提供Comparator来进行队列中的元素的排序。

SynchronousQueue:这个队列比较特殊,因为没有存储机制,实际上只是做了一个生产者和消费者的传递机制。

线程池介绍

如果任务到达时,才开始创建线程,这实际上会让任务的执行被延迟,于是产生了线程池的概念,如果在池子中已经存在了一批线程,那么任务到达时自然省去了线程创建的时间,相当于提高了响应速度。其次,如果线程执行完任务后,在放入池子中,这相当于在复用线程,达到了资源节约的目的。当然,如果任务的执行时间是远远大于线程的创建/销毁时间,其实就无所谓了。

快速创建线程池:Executors

Executors提供了一系列的快速创建线程池的方法,比如:

创建数量固定的/单个的/缓存的  线程池。

可以看到线程池的创建利用到了上面提及的BlockingQueue,队列中的元素就是任务Runnable。

方法返回的都是ExecutorService的实现类:ThreadPoolExecutor。

线程池的核心:ThreadPoolExecutor

我们直接来看看ThreadPoolExecutor的构造方法:

理解这些参数,对于理解线程池的原理有很大帮助:

corePoolSize:线程池的核心线程数量,是线程数目的一个稳定峰值。

maximumPoolSize:线程池的最大线程数量,如果corePoolSize依旧满足不了需要,那么可以让线程增长至maximumPoolSize,一旦需要下降,那么超出核心线程的那一部分线程资源将被回收。

workQueue:这个队列是待处理的任务队列。实际上,在ThreadPoolExecutor中除此之外还存在一个正在处理的工作队列workers

keepAliveTime:超过核心线程数,又小于最大线程数目的线程在空闲的情况下,多久回收。

threadFactory:线程工厂,实际上用的是默认的DefaultThreadFactory,通过代码发现仅仅是针对Thread做了些设置(比如线程组/线程名称/后台/优先级等设置),将Runnable挂到Thread上而已。

handler:如果已经达到了最大线程数目,那么对于任务只能开始拒绝了,这个就是拒绝处理的策略类。

时间: 2024-10-05 19:35:24

阻塞队列模型和线程池的相关文章

13 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件  queue队列 生产者消费者模型 Queue队列 开发一个线程池

本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Queue队列 开发一个线程池 进程 语法 进程间通讯 进程池 操作系统发展史 手工操作(无操作系统) 1946年第一台计算机诞生--20世纪50年代中期,还未出现操作系统,计算机工作采用手工操作方式. 手工操作程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把

获取synchronized锁中的阻塞队列中的线程是非公平的

synchronized中阻塞队列的线程是非公平的 测试demo: import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; public class SleepState { public static ThreadLocal<SimpleDateFormat> threadLocal = new

线程池的工作原理及使用示例

欢迎探讨,如有错误敬请指正 如需转载,请注明出处  http://www.cnblogs.com/nullzx/ 1. 为什么要使用线程池? 我们现在考虑最简单的服务器工作模型:服务器每当接收到一个客户端请求时就创建一个线程为其服务.这种模式理论上可以工作的很好,但实际上会存在一些缺陷,服务器应用程序中经常出现的情况是单个客户端请求处理的任务很简单但客户端的数目却是巨大的,因此服务器在创建和销毁线程所花费的时间和系统资源可能比处理客户端请求处理的任务花费的时间和资源更多. 线程池技术就是为了解决

Java中的线程池

综述 在我们的开发中经常会使用到多线程.例如在Android中,由于主线程的诸多限制,像网络请求等一些耗时的操作我们必须在子线程中运行.我们往往会通过new Thread来开启一个子线程,待子线程操作完成以后通过Handler切换到主线程中运行.这么以来我们无法管理我们所创建的子线程,并且无限制的创建子线程,它们相互之间竞争,很有可能由于占用过多资源而导致死机或者OOM.所以在Java中为我们提供了线程池来管理我们所创建的线程. 线程池的使用 采用线程池的好处 在这里我们首先来说一下采用线程池的

Java多线程系列--“JUC线程池”03之 线程池原理(二)

线程池示例 在分析线程池之前,先看一个简单的线程池示例. import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class ThreadPoolDemo1 { public static void main(String[] args) { // 创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newFixedThre

线程池ThreadPoolExecutor、Executors参数详解与源代码分析

欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. ThreadPoolExecutor数据成员 Private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING,0)); ctl主要用于存储线程池的工作状态以及池中正在运行的线程数.显然要在一个整型变量存储两个数据,只能将其一分为二.其中高3bit用于存储线程池的状态,低位的29bit用于存储正在运行的线程数. 线

Java并发(基础知识)—— Executor框架及线程池

在Java并发(基础知识)—— 创建.运行以及停止一个线程中讲解了两种创建线程的方式:直接继承Thread类以及实现Runnable接口并赋给Thread,这两种创建线程的方式在线程比较少的时候是没有问题的,但是当需要创建大量线程时就会出现问题,因为这种使用方法把线程创建语句随意地散落在代码中,无法统一管理线程,我们将无法管理创建线程的数量,而过量的线程创建将直接使系统崩溃. 从高内聚角度讲,我们应该创建一个统一的创建以及运行接口,为我们管理这些线程,这个统一的创建与运行接口就是JDK 5的Ex

java多线程系类:JUC线程池:03之线程池原理(二)(转)

概要 在前面一章"Java多线程系列--"JUC线程池"02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代码(基于JDK1.7.0_40)线程池源码分析(一) 创建"线程池"(二) 添加任务到"线程池"(三) 关闭"线程池" 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509954.h

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

ThreadPoolExecutor简介 ThreadPoolExecutor是线程池类.对于线程池,可以通俗的将它理解为"存放一定数量线程的一个线程集合.线程池允许同时运行的线程数量就是线程池的容量:当添加到线程池中的线程超过它的容量时,会有一部分线程阻塞等待.线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理." ThreadPoolExecutor数据结构 ThreadPoolExecutor的数据结构如下图所示: 各个数据在ThreadPoolExecutor