在并发编程中,有时候需要使用线程安全的队列,如果要实现一个线程安全的队列有两种实现方式:阻塞算法、非阻塞算法。
使用阻塞算法的队列可以用一个锁(出入队列用同一把锁),或两个锁(入队和出队用不同的锁),非阻塞的实现方式则可以用循环CAS的方式实现。
一 非阻塞方式实现线程安全的队列
ConcurrentLinkedQueue
ConcurrentLinkedQueue由head节点和tail节点组成,每个节点node由节点元素item和指向下一个节点next的引用组成。当我们增加一个元素时,它会添加到队列的末尾,当我们取一个元素时,它会返回一个队列头部的元素。
虽然ConcurrentLinkedQueue的性能很好,但是在调用size()方法的时候,会遍历一遍集合,对性能损害较大,执行很慢,因此应该尽量的减少使用这个方法,如果判断是否为空,最好用isEmpty()方法。
ConcurrentLinkedQueue不允许插入null元素,会抛出空指针异常。
ConcurrentLinkedQueue是无界的,所以使用时,一定要注意内存溢出的问题。即对并发不是很大中等的情况下使用,不然占用内存过多或者溢出,对程序的性能影响很大,甚至是致命的。
二 阻塞方式实现线程安全的对列
JDK7提供了7个阻塞队列,如下。
口 ArrayBlockingqueue:ー个由数组结构组成的有界阻塞队列。
口 LinkedBlockingqueue:一个由链表结构组成的有界阻塞队列。
口 PriorityBlockingqueue:一个支持优先级排序的无界阻塞队列。
口 Delayqueue:ー个使用优先级队列实现的无界阻塞队列。
口 Synchronousqueue:一个不存储元素的阻塞队列。
口 Linkedtransferqueue:ー个由链表结构组成的无界阻塞队列。
口 LinkedblockingDeque:ー个由链表结构组成的双向阻塞队列。
ArrayBlockingQueue
LinkedBlockingQueue
ConcurrentLinkedQueue、ArrayBlockingQueue、LinkedBlockingQueue 区别及使用场景
ArrayBlockingQueue extends AbstractQueue implements BlockingQueue LinkedBlockingQueue extends AbstractQueue implements BlockingQueue ConcurrentLinkedQueue extends AbstractQueue implements Queue
ConcurrentLinkedQueue基于CAS的无锁技术,不需要在每个操作时使用锁,所以扩展性表现要更加优异,在常见的多线程访问场景,一般可以提供较高吞吐量。
LinkedBlockingQueue内部则是基于锁,并提供了BlockingQueue的等待性方法。
ArrayBlockingQueue是初始容量固定的阻塞队列
,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。
ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列
,入队的速度很快,出队进行了加锁,性能稍慢。
LinkedBlockingQueue也是阻塞的队列,入队和出队都用了加锁
,当队空的时候线程会暂时阻塞。
LinkedBlockingQueue与ArrayBlockingQueue的异同
相同:
1、LinkedBlockingQueue和ArrayBlockingQueue都实现了BlockingQueue接口;
2、LinkedBlockingQueue和ArrayBlockingQueue都是可阻塞的队列
内部都是使用ReentrantLock和Condition来保证生产和消费的同步;
当队列为空,消费者线程被阻塞;当队列装满,生产者线程被阻塞;
使用Condition的方法来同步和通信:await()和signal()
不同:
1、由上图可以看出,他们的锁机制不同
LinkedBlockingQueue中的锁是分离的,生产者的锁PutLock,消费者的锁takeLock
而ArrayBlockingQueue生产者和消费者使用的是同一把锁;
2、他们的底层实现机制也不同
LinkedBlockingQueue内部维护的是一个链表结构
在生产和消费的时候,需要创建Node对象进行插入或移除,大批量数据的系统中,其对于GC的压力会比较大
而ArrayBlockingQueue内部维护了一个数组
在生产和消费的时候,是直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例
3、构造时候的区别
LinkedBlockingQueue有默认的容量大小为:Integer.MAX_VALUE,当然也可以传入指定的容量大小
ArrayBlockingQueue在初始化的时候,必须传入一个容量大小的值
看其提供的构造方法就能知道
4、执行clear()方法
LinkedBlockingQueue执行clear方法时,会加上两把锁
5、统计元素的个数
LinkedBlockingQueue中使用了一个AtomicInteger对象来统计元素的个数
ArrayBlockingQueue则使用int类型来统计元素
阻塞队列的实现
https://segmentfault.com/a/1190000000373535
参考: