1.队列:
定义:
一种数据结构,有非阻塞对列和阻塞对列,有界对列,无界队列
特点:先进先出
两种典型操作:
队尾添加、队头删除
几种对列:
非阻塞队列:当对列满或空时进行插入或者读取删除操作,抛出异常或者返回false,不对当前线程阻塞,没有同步或者唤醒策略。
阻塞队列:
当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。
有界队列:
一般来说队列的长度都预先定义好大小,这样的队列就是有界对列。
无界
队
列:
不具有预定义容量的队列
2.阻塞队列使用:
多线程协调,合作
线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素。
3.java中阻塞队列种类:
LinkedBlockingQueue:由链表结构组成的有界阻塞队列,此队列按 FIFO(先进先出)排序元素。此队列的默认和最大长度为Integer.MAX_VALUE。
LinkedTransferQueue:由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
ArrayBlockingQueue: 由数组结构组成的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。构造时需要指定容量,并可以选择是否需要公平性。如果公平参数被设置true,等待时间最长的线程会优先得到处理,会使你在性能上付出代价。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限,put时是不会受阻的,对列为空,take时就会阻塞。进入该队列中的元素要具有比较能力。默认情况下元素采取自然顺序排列,也可以通过比较器comparator来指定元素的排序规则。元素按照升序排列。
DelayQueue:是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。
DelayQueue运用在以下应用场景:
缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。
SynchronousQueue:不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给另外一个线程使用,SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。
4.阻塞队列控制线程操作实例:
多线程操作共同的队列时不需要额外的同步,队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距。
不需要再单独考虑同步和线程间通信的问题。
public class Test {
private int queueSize = 10;
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(queueSize);
public static void main(String[] args) {
Test test = new Test();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume() {
while(true){
try {
queue.take();
System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
while(true){
try {
queue.put(1);
System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在并发编程中,一般推荐使用阻塞队列,这样实现可以尽量地避免程序出现意外的错误。
阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。还有其他类似的场景,只要符合生产者-消费者模型的都可以使用阻塞队列。