并发队列 ConcurrentLinkedQueue 及 BlockingQueue 接口实现的四种队列

队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。

在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪种都继承自Queue。

并发队列 ConcurrentLinkedQueue 及 BlockingQueue 接口实现的四种队列_第1张图片

一、ConcurrentLinkedQueue

是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLikedQueue性能好于BlockingQueue。

它是一个基于连接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。

ConcurrentLinkedQueue重要方法:

add()和offer()都是加入元素的方法(在ConcurrentLinkedQueue中,这两个方法没有任何区别)。

poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会。

下面看一个例子:

  /* 
   * 一个基于链接节点的、无界的、线程安全的队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 
   * 是队列中时间最短的元素。新的元素插入到队列的尾部,队列检索操作从队列头部获得元素。当许多线程共享访问一个公共 collection 
   * 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许 null 元素。 
   */    
  private void concurrentLinkedQueueTest() {    
      ConcurrentLinkedQueue concurrentLinkedQueue = new ConcurrentLinkedQueue();    
      concurrentLinkedQueue.add("a");    
      concurrentLinkedQueue.add("b");    
      concurrentLinkedQueue.add("c");    
      concurrentLinkedQueue.offer("d"); // 将指定元素插入到此队列的尾部。    
      concurrentLinkedQueue.peek(); // 检索并移除此队列的头,如果此队列为空,则返回 null。    
      concurrentLinkedQueue.poll(); // 检索并移除此队列的头,如果此队列为空,则返回 null。    
  
      for (String str : concurrentLinkedQueue) {    
          System.out.println(str);    
      }    
  }  

注意:ConcurrentLinkedQueue的API.size() 是要遍历一遍集合的,速很慢,所以判空时,尽量要避免用size(),而改用isEmpty()。

二、BlockingQueue接口

ArrayBlockingQueue:基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费不能完全并行,长度是需要定义的,可以指定先进先出或者先进后出,也叫有界队列,在很多场合非常适合使用。

package concurrent;  
  
import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.TimeUnit;  
  
public class ArrayBlockingQueueTest {  
  
    private ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(10);  
  
    public static void main(String[] args) throws InterruptedException {  
        final ArrayBlockingQueueTest arrayBlockingQueueTest = new ArrayBlockingQueueTest();  
        new Thread(new Runnable() {  
            public void run() {  
                try {  
                    arrayBlockingQueueTest.producer();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  
        new Thread(new Runnable() {  
            public void run() {  
                try {  
                    arrayBlockingQueueTest.consumer();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  
  
  
    }  
  
    private void producer() throws InterruptedException {  
        for(int i=0; i<100; i++) {  
            System.out.println("arrayBlockingQueue.size()="+arrayBlockingQueue.size());  
            //Thread.sleep(1000);  
            //队列满了之后会直接抛出异常  
            //arrayBlockingQueue.add(i);  
            //队列满了之后会等待队列腾出空间  
            //arrayBlockingQueue.put(i);  
            //将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。  
            arrayBlockingQueue.offer(i);  
  
        }  
    }  
  
    private void consumer() throws InterruptedException {  
        while(true) {  
            //Thread.sleep(1000);  
            //获取并移除此队列的头部,在指定的等待时间前等待可用的元素。如果已经没有可用的元素,则没10s返回一个null  
            // System.out.println(arrayBlockingQueue.poll(10000, TimeUnit.MILLISECONDS));  
            //获取并移除此队列的头部,在元素变得可用之前一直等待  
            System.out.println(arrayBlockingQueue.take());  
            //获取但不移除此队列的头;如果此队列为空,则返回 null  
            //System.out.println(arrayBlockingQueue.peek());  
        }  
    }  
}

关于队列中各方法的说明:

操作 抛出异常 返回个特殊值 阻塞到队列可用 一定时间后退出 操作方式
添加元素 add(e) offer(e) put(e) offer(e,time,unit) 添加到队尾
移除元素 remove() poll() take() poll(e,time,unit) 获取头元素并移除
查询元素 element() peek() 获取头元素不移除

LinkedBlockingQueue:基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也是维护着一个数据缓冲队列(该队列有一个链表构成),LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作的完全并行运行。它是一个无界队列。

private LinkedBlockingQueue queue;//礼物的队列
private final static int GET_QUEUE_GIFT = 0;//从队列中获取礼物
private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case GET_QUEUE_GIFT://如果是从队列中获取礼物实体的消息
                if (!queue.isEmpty()) {
                    String vo = queue.poll();
                    if (vo != null) {//如果从队列中获取的礼物不为空,那么就将礼物展示在界面上
                        Log.e("------", "------获取的------" + vo);
                        handler.sendEmptyMessageDelayed(GET_QUEUE_GIFT, 1000);
                    }
                } else {//如果这次从队列中获取的消息是礼物是空的,则一秒之后重新获取
                    Log.e("------", "------获取的------isEmpty");
                }
                break;
        }
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    findViewById(R.id.addqueue).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            for (int i = 0; i < 6; i++) {
                queue.add("我是队列中的-----第" + (i + 6) + "个");
            }
            handler.sendEmptyMessageDelayed(GET_QUEUE_GIFT, 1000);//轮询队列获取礼物
        }
    });
    queue = new LinkedBlockingQueue<>();
    for (int i = 0; i < 6; i++) {
        queue.add("我是队列中的第" + i + "个");
    }
    handler.sendEmptyMessageDelayed(GET_QUEUE_GIFT, 1000);//轮询队列获取礼物
}

PriorityBlockingQueue:基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口),在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,他也是一个无界的队列。add()并不进行排序操作,只有在取数据时才进行排序

public static PriorityBlockingQueue queue = new PriorityBlockingQueue();  
  
public static void main(String[] args) {  
    queue.add(new User(1,"wu"));  
    queue.add(new User(5,"wu5"));  
    queue.add(new User(23,"wu23"));  
    queue.add(new User(55,"wu55"));  
    queue.add(new User(9,"wu9"));  
    queue.add(new User(3,"wu3"));  
    for (User user : queue) {  
        try {  
            System.out.println(queue.take().name);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  
  
//静态内部类  
static class User implements Comparable{  
  
    public User(int age,String name) {  
        this.age = age;  
        this.name = name;  
    }  
  
    int age;  
    String name;  
  
    @Override  
    public int compareTo(User o) {  
        return this.age > o.age ? -1 : 1;  
    }  
}  

DelayQueue:带有延迟时间的queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。

import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.DelayQueue;  
import java.util.concurrent.Delayed;  
import java.util.concurrent.TimeUnit;  
  
  
public class DelayQueueTest {  
  
    private static DelayQueue delayQueue = new DelayQueue();  
  
    private static long count = 0L;  
  
    private static final int taskNum = 4;  
  
    public static void main(String[] args) throws InterruptedException {  
  
        Object num = new Object();  
  
        final DelayQueueTest delayQueueTest = new DelayQueueTest();  
        new Thread(new Runnable() {  
            public void run() {  
                try {  
                    delayQueueTest.producer();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  
        while(true) {  
            if(delayQueue.size()==taskNum) {  
                break;  
            }  
        }  
        new Thread(new Runnable() {  
            public void run() {  
                try {  
                    delayQueueTest.consumer();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  
  
  
        new Thread(new Runnable() {  
            public void run() {  
                try {  
                    delayQueueTest.count();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  
    }  
  
    private void count() throws InterruptedException {  
        while(true) {  
            Thread.sleep(1000);  
            count++;  
            System.out.println("时间值="+count);  
            if(taskNum==count) {  
                break;  
            }  
        }  
    }  
  
    private void producer() throws InterruptedException {  
        for(int i=0; i tmpDelayedItem.removeTime) {  
                    return 1;  
                } else if(this.removeTime == tmpDelayedItem.removeTime) {  
                    return 0;  
                } else {  
                    return -1;  
                }  
            }  
            return -1;  
        }  
  
        @Override  
        public String toString() {  
            return "DelayedItem{" +  
                   "key='" + key + '\'' +  
                   ", item=" + item +  
                   ", liveTime=" + liveTime +  
                   ", removeTime=" + removeTime +  
                   '}';  
        }  
    }  
} 

运行结果:

生产者=DelayedItem{key='0', item=0, liveTime=1, removeTime=1}  
生产者=DelayedItem{key='1', item=1, liveTime=2, removeTime=2}  
生产者=DelayedItem{key='2', item=2, liveTime=3, removeTime=3}  
生产者=DelayedItem{key='3', item=3, liveTime=4, removeTime=4}  
时间值=1  
消费者=DelayedItem{key='0', item=0, liveTime=1, removeTime=1}  
时间值=1  
时间值=2  
消费者=DelayedItem{key='1', item=1, liveTime=2, removeTime=2}  
时间值=1  
时间值=2  
时间值=3  
消费者=DelayedItem{key='2', item=2, liveTime=3, removeTime=3}  
时间值=1  
时间值=2  
时间值=3  
时间值=4  
消费者=DelayedItem{key='3', item=3, liveTime=4, removeTime=4}  

SynchronousQueue:一种没有缓冲的队列,生产者产生的数据直接被消费者获取并消费。一个没有容量的并发队列有什么用了?或者说存在的意义是什么?SynchronousQueue 的实现非常复杂,SynchronousQueue 内部没有容量,但是由于一个插入操作总是对应一个移除操作,反过来同样需要满足。那么一个元素就不会再SynchronousQueue 里面长时间停留,一旦有了插入线程和移除线程,元素很快就从插入线程移交给移除线程。也就是说这更像是一种信道(管道),资源从一个方向快速传递到另一方 向。需要特别说明的是,尽管元素在SynchronousQueue 内部不会“停留”,但是并不意味之SynchronousQueue 内部没有队列。实际上SynchronousQueue 维护者线程队列,也就是插入线程或者移除线程在不同时存在的时候就会有线程队列。既然有队列,同样就有公平性和非公平性特性,公平性保证正在等待的插入线 程或者移除线程以FIFO的顺序传递资源。显然这是一种快速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入着(生产者)传递给移除着(消费者),这在多任务队列中是最快处理任务的方式。在线程池的相关章节中还会更多的提到此特性。

它模拟的功能类似于生活中一手交钱一手交货这种情形,像那种货到付款或者先付款后发货模型不适合使用SynchronousQueue。首先要知道SynchronousQueue没有容纳元素的能力,即它的isEmpty()方法总是返回true,但是给人的感觉却像是只能容纳一个元素。

import java.util.Random;  
import java.util.concurrent.SynchronousQueue;  
import java.util.concurrent.TimeUnit;  
  
public class SynchronousQueueTest {  
    public static void main(String[] args) throws InterruptedException {  
        SynchronousQueue queue = new SynchronousQueue();  
      
        new Product(queue).start();  
        new Customer(queue).start();  
    }  
    static class Product extends Thread{  
        SynchronousQueue queue;  
        public Product(SynchronousQueue queue){  
            this.queue = queue;  
        }  
        @Override  
        public void run(){  
            while(true){  
                int rand = new Random().nextInt(1000);  
                System.out.println("生产了一个产品:"+rand);  
                System.out.println("等待三秒后运送出去...");  
                try {  
                    TimeUnit.SECONDS.sleep(3);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                try {  
                    queue.put(rand);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                  
                System.out.println(queue.isEmpty());  
            }  
        }  
    }  
    static class Customer extends Thread{  
        SynchronousQueue queue;  
        public Customer(SynchronousQueue queue){  
            this.queue = queue;  
        }  
        @Override  
        public void run(){  
            while(true){  
                try {  
                    System.out.println("消费了一个产品:"+queue.take());  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                System.out.println("------------------------------------------");  
            }  
        }  
    }  
}

运行结果:

生产了一个产品:542  
等待三秒后运送出去...  
true  
消费了一个产品:542  
生产了一个产品:183  
等待三秒后运送出去...  
------------------------------------------  
true  
消费了一个产品:183  
------------------------------------------  
生产了一个产品:583  
等待三秒后运送出去...  
true  
消费了一个产品:583  
------------------------------------------  

你可能感兴趣的:(JAVA)