本篇主要聊的是一个阻塞队列,其实阻塞队列通过名字可以理解:首先其为一个队列,而这个队列是一个共享的队列,不然也就没有必要性谈阻塞了,队列自然有进有出才行。
如果所示:
补充队列和栈的区别:
- 队列: 其特点就是先进先出。简单理解好比是一节塑料管(粗细只比乒乓球大一点),然后再一边侧放入乒乓球,另一侧取出。
- 栈: 其特点是先进后出。好比是一个瓶子(粗细也只比乒乓球大一点),然后向里面放乒乓球,等你取出乒乓球的时候发现一点,最下面的乒乓球是最先放入的,却是最后才取出。
所以说阻塞队列的应该有一侧是放入线程,一遍是取出线程进行调用的。这个时候就有一个问题那就是如果队列满了,那么肯定就会阻塞线程放入,除非有线程被取出才会继续添加新线程。同样如果没有增加线程道队列中,那么取出线程的也会被阻塞,除非有新的线程放入。
所有java中就有个接口:BlockingQueue
其实在1.7之后还有一个实现BlockingQueue的子类LinkedTransferQueue,如下找了一个高版本的api文档。
但是为甚还要截图1.6呢,其实主要为了截图那个表格中的方法,具体后面聊。
既然是多线程,那为什么又要有一个接口类作为阻塞队列呢?
因为硬件条件无法支持无穷多的线程,就像是人多好办事,一个工厂就10个人的活,如果从1–到10个人依次会增加生产效率的,但是如果直接弄100个人,就有一个问题了人均效率低了不说,工厂还要管100个人吃饭。硬件也是如此其随着线程的增多会有一个峰值,然后就是效率下滑,所以就需要一个东西去控制同时运行的线程数。
而且需要这样一个功能类,程序员不需要自己写了,可以通过调用BlockingQueue接口的实现类即可。现象我们依次聊一下其实现类。
阻塞队列接口的实现类
阻塞
实现类 | 描述 |
---|---|
ArrayBlockingQueue (常用) | 掌握 |
LinkedBlockingQueue(常用) | 掌握 |
DelayQueue | 了解 |
ArrayBlockingQueue (常用)
看一下官网的API:
可以看出ArrayBlockingQueue是基于数组的阻塞队列实现,其内部维护了一个定长数组,以便缓存队列中的数据对象。其除了定长数组以外,ArrayBockingQueue内部还保存着两个整形变量,分分别标识着队列的头部和尾部再数据中的位置。
**队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。 **
这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入
元素会导致操作受阻塞;试图从空队列中提取
元素将导致类似阻塞。
其锁是一个共享锁
而且默认是非公平锁,当然也可以进行设置而选择公平或者非公共锁。
LinkedBlockingQueue (常用)
LinkedBlockingQueue是基于链表(如果不懂什么是链表可以看下传送阵)的阻塞队列,其内部维持着数据缓存的对象是一个链表构成。当生产者往队列中放入一个数据时,队列会从生产者中获取数据,并缓存再队列内部,而生产者立即返回。
只有当队列缓存值大于最大值的缓存数量时(构造时候可以定义,如果不定义默认是数字最大值),才会阻塞生成者放入数据。等取出一个数据后才会继续放入,反之去出也是空的时候阻塞,等加入的时候才会唤醒。
其与ArrayBlockingQueue不同,因为生产者和消费者采取的独立的锁来控制数据的同步,也就意味着在高并发的情况下生产者和消费者可以并行操作队列的数据,以此俩提高整个对象的并发性能。
DelayQueue
DelayQueue中的元素只有档期指定的时间到了,才能够从队列中获取该元素。DelayQueue还有一个特点那就是没有大小限制,也就是无界限阻塞队列,所以其生产者放入队列永远不会阻塞,只有读取数据时候没有数据才会阻塞。
PriorityBlockingQueue
PriorityBlockingQueue是基于优先级排序的队列,其优先级需要通过构造函数传入的Compator对象来决定的,但是主语PriorityBlockingQueue也是一个无界队列,其不会阻塞生产者,但是会阻塞消费者。
因为有这个特性所以就要注意:生产者生产数据的速度绝对不能快于消费者消费的数据速度。不然随着时间增长会无限占据堆可用内存。
不过还有一点需要注意,其控制线程的同步锁采用的公平锁。
SynchronousQueue
SynchronousQueue这个队列其实有点像是打着羊头卖狗肉的感觉,说是队列,其不会缓存任何数据,而是生产一个数据然后消费一个数据。说白了就是队列长度为1的单个队列。
LinkedTransferQueue
LinkedTransferQueue是一个链表结构的队列,其实看名字也猜到了,其是一个无界TransferQueue队列.。相对于其它的队列,其多了tryTransfer和transfer方法。
LinkedTransferQueue采用的是一种预占模式。简单的说就是消费者提取数据时:
- 如果队列为不为空,就直接拿走。
- 如果为空的话的话,就是是在某个取数据的节点上等着,生产者生产就不随便放数据了而是放在这个节点,消费者直接拿走。
这个说白了就是可以提前预定然后来了就去拿。
LinkedBlockingDeque
LinkedBlockingDeque是一个由链表结构组成双向阻塞队列,也就是可以从队列两端插入和取出数据。
对于一些指定的操作,在插入和取出数据时候如果队列状态不允许该操作可能回阻塞该线程直到队列状态变更为运行操作。一般会有两种状态:
-
插入数据
如果当前队列已满将进入阻塞状态,一直等待队列由空的位置时再将该数据插入,该操作可以设置超时参数,超时后返回false表示操作识别,也可以不设置超时参数,一直阻塞,中断后会抛出插入失败异常(InterruptedException)。
-
读取数据
如果当前队列为空会阻塞队列,不为空即返回数据,同样也可以设置超时参数。
核心方法
其实在最前面截图的时候说过我截图 一个1.6版本的以及一个1.8版本的blickingqueue队列,牧目的是为了截图:
上面的意思是插入和移除数据各自有四种方法,但是这四种形式的处理方式不同。
引起队列的其中最重要的方法差不多,所以就通过演示一个队列的方法,看一下:
抛出异常
开始演示:
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
abq.add("a");
abq.add("b");
System.out.println(abq.element());
abq.add("c");
可以看出通过add方法添加数据,如果队列满了就会报错。
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
abq.add("a");
abq.add("b");
System.out.println("abq.element()----"+abq.element());
System.out.println("第一次移除:---"+ abq.remove());
System.out.println("abq.element()----"+abq.element());
System.out.println("第二次移除:---"+ abq.remove());
System.out.println("第三次次移除:---"+ abq.remove());
可以看出如果数据为空继续移除也会报错。
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
abq.add("a");
abq.add("b");
System.out.println("abq.element()----"+abq.element());
System.out.println("第一次移除:---"+ abq.remove());
System.out.println("abq.element()----"+abq.element());
System.out.println("第二次移除:---"+ abq.remove());
System.out.println("abq.element()----"+abq.element());
element方法报错,其做哟是检查即将取出的数据,毕竟已经没有数据,所以就报错了。
特殊值
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
// 这里混合用也没有关系,最主要的是超过其长度时候的用的是add还是offer
abq.offer("a");
abq.add("b");
System.out.println(abq.offer("c"));;
可以看出超出其长度的时候回自动返回一个false。
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
// 这里混合用也没有关系,最主要的是超过其长度时候的用的是add还是offer
abq.offer("a");
abq.add("b");
abq.poll();
abq.remove( );
System.out.println(abq.poll());
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
// 这里混合用也没有关系,最主要的是超过其长度时候的用的是add还是offer
abq.offer("a");
abq.add("b");
System.out.println("第一次peek "+abq.peek());
abq.poll();
System.out.println("第二次peek "+abq.peek());
abq.remove( );
System.out.println("第三次peek "+abq.peek());
阻塞
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
// 这里混合用也没有关系,最主要的是超过其长度时候的用的是是什么添加方法
abq.offer("a");
abq.add("b");
abq.put("c");
可以看出其阻塞状态。
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
// 这里混合用也没有关系,最主要的是超过其长度时候的用的是add还是offer
abq.offer("a");
abq.add("b");
System.out.println("第一种方法 :" + abq.remove());
System.out.println("take() 第一种方法 :" + abq.take());
System.out.println("take() 第一种方法 :" + abq.take());
至于检测方式,就没有必要了毕竟处于阻塞状态,你检测什么啊。
超时
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
// 这里混合用也没有关系,最主要的是超过其长度时候的用的是
abq.add("a");
abq.offer("b");
System.out.println("超时添加 "+abq.offer("c",1,TimeUnit.SECONDS));
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(2);
// 这里混合用也没有关系,最主要的是超过其长度时候的用的是
abq.add("a");
abq.offer("b");
abq.remove();
abq.poll();
System.out.println("超时取出 "+abq.poll(1,TimeUnit.SECONDS));
本篇也就是聊了一些理论和一些常用方法演示。