JUC------阻塞队列

文章预览

前言

阻塞队列BlockingQueue是一个支持两个附加操作的队列。提供了一些阻塞方法,主要作用是:

1) 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满。

2) 支持阻塞的移除方法:在队列为空时,获取元素的线程会等待队列变为非空。

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列是生存者用来存放元素,消费者用来获取元素的容器。
JUC------阻塞队列

一、阻塞队列类型

DK提供了7种阻塞队列。如下:

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue:支持优先级排序的*阻塞队列
  • DelayQueue:使用优先级队列实现的*阻塞队列
  • SynchronousQueue:不存储元素的阻塞队列
  • LinkedTransferQueue:由链表结构组成的*阻塞队列
  • LinkedBlockingQueue:由链表结构组成的双向阻塞队列

1.1、ArrayBlockingQueue

ArrayBlockingQueue是一个用数组实现的有界阻塞队列,在初始化构造的时候需要***指定队列的容量***。此队列按照先进先出(FIFO)的原则对元素进行排序。
具有如下特点:

  • 队列的容量一旦在构造时指定,后续不能改变;
  • 插入元素时,在队尾进行;删除元素时,在队首进行;
  • 队列满时,调用特定方法插入元素会阻塞线程;队列空时,删除元素也会阻塞队列;
  • 支持公平/非公平策略,默认为非公平策略。
     这里的公平策略,是指当线程从阻塞到唤醒后,以最初请求的顺序(FIFO)来添加或删除元素;非公平策略指线程被唤醒后,谁先抢占到锁,谁就能往队列中添加/删除,顺序是随机的。

1.2、LinkedBlockingQueue

LinkedBlockingQueue是一个用***链表***实现的***有界阻塞队列***。此队列的默认和***最大长度***是Integer.MAX_VALUE。此队列按照***先进先出***(FIFO)的原则对元素进行排序。

LinkedBlockingQueue除了底层***数据结构是单链表***与ArrayBlockingQueue不同外,另外一个特点就是:它维护了两把锁——takeLockputLock

takeLock用于控制出队的并发,putLock用于控制入队的并发。

这也就意味着,同一时刻,只能只有一个线程能执行入队/出队操作,其余入队/出队线程会被阻塞; 但是,入队和出队之间可以并发执行,即同一时刻,可以同时有一个线程进行入队,另一个线程进行出队,这样就可以提升吞吐量。
***LinkedBlockingQueue***和***ArrayBlockingQueue***比较主要有以下区别:

  • 队列大小不同。ArrayBlockingQueue初始构造时必须指定大小,而LinkedBlockingQueue构造时既可以指定大小,也可以不指定(默认为Integer.MAX_VALUE,近似于*);
  • 底层数据结构不同。ArrayBlockingQueue底层采用数组作为数据存储容器,而LinkedBlockingQueue底层采用单链表作为数据存储容器;
  • 两者的加锁机制不同。ArrayBlockingQueue使用一把全局锁,即入队和出队使用同一个ReentrantLock锁;而LinkedBlockingQueue进行了锁分离,入队使用一个ReentrantLock锁(putLock),出队使用另一个ReentrantLock锁(takeLock);
  • LinkedBlockingQueue不能指定公平/非公平策略(默认都是非公平),而ArrayBlockingQueue可以指定策略。

1.3、PriorityBlockingQueue

PriorityBlockingQueue是一个支持优先级的****阻塞队列***。默认情况下元素采用自然顺序升序排列。在构造的时候可以指定队列的初始容量。

  • PriorityBlockingQueue是一种优先级队列,也就是元素并不是以FIFO的方式出/入队,而是按照权重大小的顺序出队;
  • PriorityBlockingQueue是真正的*队列(仅受内存大小限制),它不像ArrayBlockingQueue那样构造时必须指定最大容量,也不像LinkedBlockingQueue默认最大容量为Integer.MAX_VALUE;
  • 由于PriorityBlockingQueue是按照元素的权重进入排序,所以队列中的元素必须是可以比较的,也就是说元素必须实现Comparable接口;
  • 由于PriorityBlockingQueue*队列,所以插入元素永远不会阻塞线程;
  • PriorityBlockingQueue底层是一种基于数组实现的堆结构。

1.4、SynchronousQueue

SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。

它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。通过构造入参创建公平性访问的SynchronousQueue,如果设为true,则等待的线程会采用先进先出的顺序访问队列。

SynchronousQueue的底层实现包含两种数据结构——栈和队列。这是一种非常特殊的阻塞队列。

特点简要概述如下:

  • 入队线程和出队线程必须一一匹配,否则任意先到达的线程会阻塞。比如ThreadA进行入队操作,在有其它线程执行出队操作之前,ThreadA会一直等待,反之亦然;
  • SynchronousQueue内部不保存任何元素,也就是说它的容量为0,数据直接在配对的生产者和消费者线程之间传递,不会将数据缓存到队列中。
  • SynchronousQueue支持公平/非公平策略。其中非公平模式,基于内部数据结构——“栈”来实现,公平模式,基于内部数据结构——“队列”来实现;

1.5、DelayQueue

DelayQueue是一个支持延时获取元素的*阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。

DelayQueue非常有用,可以将DelayQueue运用在以下应用场景

  • 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
  • 定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间。

它的特点:

  • DelayQueue是*阻塞队列;
  • 队列中的元素必须实现Delayed接口,元素过期后才会从队列中取走;

1.6、LinkedBlockingDeque

LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。

和ConcurrentLinkedDeque类似,都是一种双端队列的结构,只不过LinkedBlockingDeque同时也是一种阻塞队列。

LinkedBlockingDeque底层利用ReentrantLock实现同步,并不像ConcurrentLinkedDeque那样采用***无锁算法***。

1.7、LinkedTransferQueue

LinkedTransferQueue一种比较***特殊***的阻塞队列。

我们知道,在普通阻塞队列中,当队列为空时,消费者线程(调用take或poll方法的线程)一般会阻塞等待生产者线程往队列中存入元素。而LinkedTransferQueue的transfer方法则比较特殊:

  • 当有消费者线程阻塞等待时,调用transfer方法的生产者线程不会将元素存入队列,而是直接将元素传递给消费者;
  • 如果调用transfer方法的生产者线程发现没有正在等待的消费者线程,则会将元素入队,然后会阻塞等待,直到有一个消费者线程来获取该元素。

LinkedTransferQueue的特点简要概括如下:

  • LinkedTransferQueue是一种*阻塞队列,底层基于单链表实现;
  • LinkedTransferQueue中的结点有两种类型:数据结点、请求结点;
  • LinkedTransferQueue基于无锁算法实现。

LinkedTransferQueue兼具了SynchronousQueue的特性以及无锁算法的性能,并且是一种*队列:

和SynchronousQueue相比,LinkedTransferQueue可以存储实际的数据;

和其它阻塞队列相比,LinkedTransferQueue直接用无锁算法实现,性能有所提升。

二、ArrayBlockingQueue常用方法

增加值的方法:

  • add(): 在不超出队列长度的情况下插入元素,可以立即执行,成功返回true,如果队列满了就抛出异常,其底层实现的是offer方法,不会阻塞。
  • offer(): 在不超出队列长度的情况下插入元素的时候则可以立即在队列的尾部插入指定元素,成功时返回true,如果此队列已满,则返回false。不会阻塞。
  • put(): 插入元素的时候,如果队列满了就进行等待,直到队列可用

取值的方法:

  • remove():底层是用到了poll()方法,检索并且删除返回队列头的元素,与poll()方法不同的是,元素没有是进行抛异常NoSuchElementException。
  • poll(): 检索并且删除返回队列头的元素,有就返回没有就返回null。
  • take(): 检索并且删除返回队列头的元素,如果元素没有会一直等待,有就返回。
  • peek(): 检索但不移除此队列的头部;如果此队列为空,则返回null。返回头部元素。

三、LinkedBlockingQueue 常用方法

  • offer(E e): 将给定的元素设置到队列中,如果设置成功返回true, 否则返回false. e的值不能为空,否则抛出空指针异常。
  • offer(E e, long timeout, TimeUnit unit): 将给定元素在给定的时间内设置到队列中,如果设置成功返回true, 否则返回false.
  • add(E e): 将给定元素设置到队列中,如果设置成功返回true, 否则抛出异常。如果是往限定了长度的队列中设置值,推荐使用offer()方法。
  • put(E e): 将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间。
  • take(): 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。
  • poll(long timeout, TimeUnit unit): 在给定的时间里,从队列中获取值,如果没有取到会抛出异常。
  • remainingCapacity():获取队列中剩余的空间。
  • remove(Object o): 从队列中移除指定的值。
  • contains(Object o): 判断队列中是否拥有该值。
  • drainTo(Collection c): 将队列中值,全部移除,并发设置到给定的集合中。
上一篇:【shell 脚本】删除/var/log 下的日志


下一篇:Doug Lea文章阅读记录-JUC系列