阻塞队列,学习它的目的就是为了更好地理解线程池。

阻塞队列:

阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元 素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

常见的阻塞场景:

(1)当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队 列。

(2)当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的 位置,线程被自动唤醒。

支持以上两种阻塞场景的队列被称为阻塞队列。

 

BlockingQueue的核心方法:【摘抄自别人的博客】

(1)放入数据:

• offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里。即如果BlockingQueue可以 容纳,则返回true,否则返回false。(本方法不阻塞当前执行方法的线程。)

• offer(E o,long timeout,TimeUnit unit):可以设定等待的时间。如果在指定的时间内还不能往队列 中加入BlockingQueue,则返回失败。

• put(anObject):将anObject加到BlockingQueue里。如果BlockQueue没有空间,则调用此方法的线程 被阻断,直到BlockingQueue里面有空间再继续。

(2)获取数据:

• poll(time):取走 BlockingQueue 里排在首位的对象。若不能立即取出,则可以等 time参数规定的时 间。取不到时返回null。

• poll(long timeout,TimeUnit unit):从BlockingQueue中取出一个队首的对象。如果在指定时间内, 队列一旦有数据可取,则立即返回队列中的数据;否则直到时间超时还没有数据可取,返回失败。

• take():取走BlockingQueue里排在首位的对象。若BlockingQueue为空,则阻断进入等待状态,直 到 BlockingQueue有新的数据被加入。

• drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数)。通 过该方法,可以提升获取数据的效率;无须多次分批加锁或释放锁。

Java中的阻塞队列:

在Java中提供了7个阻塞队列:

• ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

• LinkedBlockingQueue:由链表结构组成的有界阻塞队列。

• PriorityBlockingQueue:支持优先级排序的无界阻塞队列。

• DelayQueue:使用优先级队列实现的无界阻塞队列。

• SynchronousQueue:不存储元素的阻塞队列。

• LinkedTransferQueue:由链表结构组成的无界阻塞队列。

• LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

阻塞队列的实现原理:(看源码)

阻塞队列(接以前文章的线程池)

ArrayBlockingQueue 是维护一个 Object 类型的数组,takeIndex 和putIndex分别 表示队首元素和队尾元素的下标,count表示队列中元素的个数,lock则是一个可重入锁,notEmpty和notFull 是等待条件。

再进源码,看方法put():

阻塞队列(接以前文章的线程池)

它先获取了锁,并且获取的是可中断锁,然后判断当前元素个数是否等于 数组的长度,如果相等,则调用notFull.await()进行等待。当此线程被其他线程唤醒时,通过 enqueue(e)方法插入元素,最后解锁。

看enqueue()方法:

阻塞队列(接以前文章的线程池)

插入成功后,通过notEmpty唤醒正在等待取元素的线程。

看看take()方法:

阻塞队列(接以前文章的线程池)

跟put方法实现类似,put方法等待的是notFull信号,而take方法等待的是notEmpty信号。在take方法中, 如果可以取元素,则通过dequeue方法取得元素。

看dequeue()方法:

阻塞队列(接以前文章的线程池)

跟enqueue方法类似,在获取元素后,通过notFull的signal方法来唤醒正在等待插入元素的线程。


阻塞队列的使用场景:[代码示例来自鸿神]

除了线程池的实现使用阻塞队列外,我们还可以在生产者-消费者模式中使用阻塞队列:首先使用 Object.wait()、Object.notify()和非阻塞队列实现生产者-消费者模式。

阻塞队列(接以前文章的线程池)

阻塞队列(接以前文章的线程池)

下面使用阻塞队列实现的生产者-消费者模式:

阻塞队列(接以前文章的线程池)

阻塞队列(接以前文章的线程池)

使用阻塞队列实现无须单独考虑同步和线程间通信的问题,其实现起来很简单。


7个阻塞队列的详细介绍:【摘抄自别人的博客】:

1.ArrayBlockingQueue 它是用数组实现的有界阻塞队列,并按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保 证线程公平地访问队列。公平访问队列就是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以 按照阻塞的先后顺序访问队列。即先阻塞的生产者线程,可以先往队列里插入元素;先阻塞的消费者线 程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。我们可以使用以下代码创建一 个公平的阻塞队列,如下所示: ArrayBlockingQueue fairQueue=new ArrayBlockingQueue(2000,true);

2.LinkedBlockingQueue 它是基于链表的阻塞队列,同ArrayListBlockingQueue类似,此队列按照先进先出(FIFO)的原则对元 素进行排序,其内部也维持着一个数据缓冲队列(该队列由一个链表构成)。当生产者往队列中放入一个 数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到 缓存容量的最大值时(LinkedBlockingQueue可以通过构造方法指定该值),才会阻塞生产者队列,直到消 费者从队列中消费掉一份数据,生产者线程会被唤醒。反之,对于消费者这端的处理也基于同样的原理。 而LinkedBlockingQueue之所以能够高效地处理并发数据,还因为其对于生产者端和消费者端分别采用了独 立的锁来控制数据同步。这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以 此来提高整个队列的并发性能。作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对 象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量 (Integer.MAX_VALUE)。这样一来,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满 阻塞产生,系统内存就有可能已被消耗殆尽了。ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也 是最常用的阻塞队列。一般情况下,在处理多线程间的生产者-消费者问题时,使用这两个类足已。

3.PriorityBlockingQueue 它是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。这里可以自定义实现 compareTo()方法来指定元素进行排序规则;或者初始化PriorityBlockingQueue时,指定构造参数 Comparator来对元素进行排序。但其不能保证同优先级元素的顺序。

4.DelayQueue 它是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现 Delayed 接口。创建元素时,可以指定元素到期的时间,只有在元素到期时才能从队列中取走。

5.SynchronousQueue 它是一个不存储元素的阻塞队列。每个插入操作必须等待另一个线程的移除操作,同样任何一个移除 操作都等待另一个线程的插入操作。因此此队列内部其实没有任何一个元素,或者说容量是0,严格来说它 并不是一种容器。由于队列没有容量,因此不能调用peek操作(返回队列的头元素)。

6.LinkedTransferQueue 它是一个由链表结构组成的无界阻塞TransferQueue队列。LinkedTransferQueue实现了一个重要的接口 TransferQueue。该接口含有5个方法,其中有3个重要的方法,它们分别如下所示。 (1)transfer(E e):若当前存在一个正在等待获取的消费者线程,则立刻将元素传递给消费者;如 果没有消费者在等待接收数据,就会将元素插入到队列尾部,并且等待进入阻塞状态,直到有消费者线程 取走该元素。 (2)tryTransfer(E e):若当前存在一个正在等待获取的消费者线程,则立刻将元素传递给消费者; 若不存在,则返回 false,并且不进入队列,这是一个不阻塞的操作。与 transfer 方法不同的是,tryTransfer 方法无论消费者是否接收,其都会立即返回;而transfer方法则是消费者接收了才返回。 (3)tryTransfer(E e,long timeout,TimeUnit unit):若当前存在一个正在等待获取的消费者线程, 则立刻将元素传递给消费者;若不存在则将元素插入到队列尾部,并且等待消费者线程取走该元素。若在 指定的超时时间内元素未被消费者线程获取,则返回false;若在指定的超时时间内其被消费者线程获取, 则返回true。

7.LinkedBlockingDeque 它是一个由链表结构组成的双向阻塞队列。双向队列可以从队列的两端插入和移出元素,因此在多线 程同时入队时,也就减少了一半的竞争。由于是双向的,因此LinkedBlockingDeque多了addFirst、addLast、 offerFirst、offerLast、peekFirst、peekLast等方法。其中,以First单词结尾的方法,表示插入、获取或移除双 端队列的第一个元素;以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。

相关文章: