在并发队列上JDK提供了两套实现,一个是已ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue为代表的阻塞队列。两种都是继承了Queue接口。
层次图(hierarchy):(idea中 层次图快捷键:选中后(或在类中空白处)) ctrl+h )
1.concurrentLinkedQueue:
concurrentLinkedQueue是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常concurrentLinkedQueue性能要好于BlockedQueue。它是一个基于链接节点的无界线程安全队列。该队列元素遵循先进先出的原则。头是最先加入的,尾是最后加入的,该队列不允许有null元素。
ConcurrentLinkedQueue重要方法:add()、offer()、poll()、peek()
1. add()和offer()都是加入元素的方法(在concurrentLinkedQueue中,这两个方法没有任何区别,之所以有两个相同的方法,是因为这两个方法都继承自Queue,其他场景可能不一样)。
2. pool()和peek()都是取头元素,区别在于前者会删除元素,后者不会
验证ConcurrentLinkedQueue是线程安全的:
package com.company;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Created by BaiTianShi on 2018/8/18.
*/
public class QueueTest {
public static void main(String[] args) {
ConcurrentLinkedQueue<String> qu = new ConcurrentLinkedQueue<>();
qu.add("a");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
if(!qu.isEmpty()){
System.out.println("进入t1线程");
System.out.println("t1获取的元素"+qu.poll());
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
if(!qu.isEmpty()){
System.out.println("进入t2线程");
System.out.println("t2获取的元素"+qu.poll());
}
}
},"t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
if(!qu.isEmpty()){
System.out.println("进入t3线程");
System.out.println("t3获取的元素"+qu.poll());
}
}
},"t3");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
if(!qu.isEmpty()){
System.out.println("进入t4线程");
System.out.println("t4获取的元素"+qu.poll());
}
}
},"t4");
Thread t5 = new Thread(new Runnable() {
@Override
public void run() {
if(!qu.isEmpty()){
System.out.println("进入t5线程");
System.out.println("t5获取的元素"+qu.poll());
}
}
},"t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
运行结果:
进入t1线程
进入t3线程
进入t5线程
进入t2线程
t5获取的元素null
t3获取的元素null
t1获取的元素a
进入t4线程
t2获取的元素null
t4获取的元素null
可见看到,只有一个能拿到非空的值。其他线程拿不到a。在判断队列是否为空时,不要使用size,因为size要遍历一遍集合,使用isEmpty效率比较高,size()方法见下图。
ArrayBlockQueue
基于数组的阻塞队列实现,在ArrayBlockQueue内部维护里一个定长的数组,以便缓存队列中的数据对象,