转自:Java阻塞队列ArrayBlockingQueue和LinkedBlockingQueue实现原理分析
Java中的阻塞队列接口BlockingQueue继承自Queue接口。
BlockingQueue接口提供了3个添加元素方法。
add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常
offer:添加元素到队列里,添加成功返回true,添加失败返回false
put:添加元素到队列里,如果容量满了会阻塞直到容量不满
3个删除方法。
poll:删除队列头部元素,如果队列为空,返回null。否则返回元素。
remove:基于对象找到对应的元素,并删除。删除成功返回true,否则返回false
take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除
常用的阻塞队列具体类有ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、LinkedBlockingDeque等。
本文以ArrayBlockingQueue和LinkedBlockingQueue为例,分析它们的实现原理。
ArrayBlockingQueue
ArrayBlockingQueue的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制(classic two-condition algorithm)。
ArrayBlockingQueue是一个带有长度的阻塞队列,初始化的时候必须要指定队列长度,且指定长度之后不允许进行修改。
它带有的属性如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
数据的添加
ArrayBlockingQueue有不同的几个数据添加方法,add、offer、put方法。
add方法:
- 1
- 2
- 3
- 4
- 5
- 6
add方法内部调用offer方法如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
insert方法如下:
- 1
- 2
- 3
- 4
- 5
- 6
put方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
ArrayBlockingQueue的添加数据方法有add,put,offer这3个方法,总结如下:
add方法内部调用offer方法,如果队列满了,抛出IllegalStateException异常,否则返回true
offer方法如果队列满了,返回false,否则返回true
add方法和offer方法不会阻塞线程,put方法如果队列满了会阻塞线程,直到有线程消费了队列里的数据才有可能被唤醒。
这3个方法内部都会使用可重入锁保证原子性。
数据的删除
ArrayBlockingQueue有不同的几个数据删除方法,poll、take、remove方法。
poll方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
poll方法内部调用extract方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
take方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
remove方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
removeAt方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
ArrayBlockingQueue的删除数据方法有poll,take,remove这3个方法,总结如下:
poll方法对于队列为空的情况,返回null,否则返回队列头部元素。
remove方法取的元素是基于对象的下标值,删除成功返回true,否则返回false。
poll方法和remove方法不会阻塞线程。
take方法对于队列为空的情况,会阻塞并挂起当前线程,直到有数据加入到队列中。
这3个方法内部都会调用notFull.signal方法通知正在等待队列满情况下的阻塞线程。
LinkedBlockingQueue
LinkedBlockingQueue是一个使用链表完成队列操作的阻塞队列。链表是单向链表,而不是双向链表。
内部使用放锁和拿锁,这两个锁实现阻塞(“two lock queue” algorithm)。
它带有的属性如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。
而LinkedBlockingQueue有2个锁,放锁和拿锁,添加数据和删除数据是可以并行进行的,当然添加数据和删除数据的时候只能有1个线程各自执行。
数据的添加
LinkedBlockingQueue有不同的几个数据添加方法,add、offer、put方法。
add方法内部调用offer方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
put方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
LinkedBlockingQueue的添加数据方法add,put,offer跟ArrayBlockingQueue一样,不同的是它们的底层实现不一样。
ArrayBlockingQueue中放入数据阻塞的时候,需要消费数据才能唤醒。
而LinkedBlockingQueue中放入数据阻塞的时候,因为它内部有2个锁,可以并行执行放入数据和消费数据,不仅在消费数据的时候进行唤醒插入阻塞的线程,同时在插入的时候如果容量还没满,也会唤醒插入阻塞的线程。
数据的删除
LinkedBlockingQueue有不同的几个数据删除方法,poll、take、remove方法。
poll方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
take方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
remove方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
LinkedBlockingQueue的take方法对于没数据的情况下会阻塞,poll方法删除链表头结点,remove方法删除指定的对象。
需要注意的是remove方法由于要删除的数据的位置不确定,需要2个锁同时加锁。