学习ConcurrentLinkedQueue

简介: 原因:学习ConcurrentLinkedQueue是看到akka框架的默认邮箱是使用ConcurrentLinkedQueue实现的。   1. ConcurrentLinkedQueue在java.

 

原因:学习ConcurrentLinkedQueue是看到akka框架的默认邮箱是使用ConcurrentLinkedQueue实现的。

 

1. ConcurrentLinkedQueue在java.util.concurrent包中(java 版本是1.7.0_71),类间集成关系如下:

public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
        implements Queue<E>, java.io.Serializable

ConcurrentLinkedQueue继承了抽象类AbstractQueue,AbstractQueue抽象类中的几个实现方法也都是利用Queue接口中的方法实现的。

Queue接口中定义的抽象方法有:

package java.util;

public interface Queue<E> extends Collection<E> {

    // 向队列中插入元素e,不验证队列空间限制条件下插入一个元素。如果队列有剩余空间,直接插入;如果队列满了,就抛出IllegalStateException异常
    boolean add(E e);
    
    // 同样是向队列中插入元素e。如果队列有空间限制,同add;如果队列没有空间限制,比如ConcurrentLinkedQueue,总是可以插入进去
    boolean offer(E e);
    
    // 返回并删除队列头部的第一个元素,remove()与poll()方法的不同在于,如果队列为空,remove()方法会抛出异常,而poll()方法是返回null
    E remove();
    
    // 返回并删除队列头部的第一元素,如果队列空,返回null
    E poll();
    
    // 返回但是不删除队头元素,element()方法与peek()方法的不同在于,如果队列为空,element()方法会抛出NoSuchElementException,而peek()方法返回null
    E element();
    
    // 返回队头元素,如果队列为空,返回null
    E peek();
} 

队列的操作无非就是上述的插入和删除操作,从上述方法的定义来看,优先使用offer()和poll(),因为不抛异常的方法比较容易处理。

 

2. ConcurrentLinkedQueue是什么?

    ConcurrentLinkedQueue是基于链接节点实现的无界的线程安全的先进先出的非阻塞的队列。其链接节点的结构为:

    private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
    }

   每一个链接节点(Node)包含节点元素(item)和指向下一个节点的引用(next)。ConcurrentLinkedQueue包含一个头结点和一个尾节点

    // 头结点,所有后继节点都可以从head开始,使用succ()方法访问到
private transient volatile Node<E> head; // 尾节点, private transient volatile Node<E> tail;

  头尾节点都提到了succ()方法,succ()方法是

    // sicc()方法是返回节点p的后继节点。如果节点p的后继节点指向自己,则返回头结点。这种情况是如何发生的?(节点p已经不在链表中了?)
    final Node<E> succ(Node<E> p) {
        Node<E> next = p.next;
        return (p == next) ? head : next;
    }

 succ()方法主要用途有什么?

   (1). 求队列大小  

    // 返回队列中元素个数,可以看到元素个数是int类型, 如果元素个数超过了Integer.MAX_VALUE的话,也只能返回Integer.MAX_VALUE
    // 另外,这个方法返回的值是不精确的。当然我们不是来看size()方法的,是来看succ()方法是如何使用的。
    public int size() {
        int count = 0; 
        // 从第一个节点开始遍历,如果节点不为null,统计节点个数,然后使用succ()方法获取下一个节点
        for (Node<E> p = first(); p != null; p = succ(p))
            if (p.item != null)
                // Collection.size() spec says to max out
                if (++count == Integer.MAX_VALUE)
                    break;
        return count;
    }

  

   (2). contains()方法中succ()的用法与求队列大小类似

    public boolean contains(Object o) {
        if (o == null) return false;
        for (Node<E> p = first(); p != null; p = succ(p)) {
            E item = p.item;
            if (item != null && o.equals(item))
                return true;
        }
        return false;
    }

  

3. ConcurrentLinkedQueue的构造函数为:

    public ConcurrentLinkedQueue() {
        head = tail = new Node<E>(null);
    }

 从构造函数看,ConcurrentLinkedQueue的头结点是包含null元素的一个节点,并且初始条件下head节点指向tail节点。

 接下来看下head和tail是如何在offer()和poll()方法中怎么使用的。

// 插入元素到队尾
public boolean offer(E e) {
  // 检查元素e是否为null,如果为null,抛出NullPointerException
  checkNotNull(e);
  // 创建新节点newNode
  final java.util.concurrent.ConcurrentLinkedQueue.Node<E>
      newNode = new java.util.concurrent.ConcurrentLinkedQueue.Node<E>(e);
  // 首先赋值tail给t (t = tail),赋值t给p (p = t)
  // 然后执行死循环for(;;)
  for (java.util.concurrent.ConcurrentLinkedQueue.Node<E> t = tail, p = t;;) {
    // 将p的next赋值给q, p.next -> q
    java.util.concurrent.ConcurrentLinkedQueue.Node<E> q = p.next;
    // 如果q为null,表示p是尾节点
    if (q == null) {
      // p是尾节点,将新节点newNode赋值给p的next,p.next -> e(newNode)
      // 这个赋值过程是使用CAS来实现的,CAS比较并交换,意思就是如果newNode != null,则交换他们
      if (p.casNext(null, newNode)) {
        // 如果p != t,即p != t = tail,表示t(= tail)不是尾节点
        if (p != t)
          // 将t置为尾节点,该操作允许失败,因此t(= tail)并不总是尾节点
          // 因此需要执行for(;;),先找到尾节点
          casTail(t, newNode);  // Failure is OK.
        return true;
      }
      // Lost CAS race to another thread; re-read next
    }
    else if (p == q)
      // 如果p == q, 说明尾节点tail已经不在链表中了,
      // 这种情况下,跳转到head,因为从head开始所有的节点都可达
      p = (t != (t = tail)) ? t : head;
    else
      // 如果p == q且q == null,p指向q,即p跳转到下一个元素
      p = (p != t && t != (t = tail)) ? t : q;
  }
}

  

public E poll() {
  // 跳出for(;;)循环的标志位
  restartFromHead:
  for (;;) {
    // 首先赋值head给h (h = head),赋值h给p (p = h),并定义变量q
    // 然后执行死循环for(;;)
    for (java.util.concurrent.ConcurrentLinkedQueue.Node<E> h = head, p = h, q;;) {
      // 获取p的元素值,即头节点的元素值
      E item = p.item;
      // 如果元素值不为null,并将p的元素置null
      // casItem(item, null)意思是如果item != null,则交换两者
      // 交换之后,item就从队列中被移除了
      if (item != null && p.casItem(item, null)) {
        if (p != h) // 如果p不是指向h (head),更新head的值
          updateHead(h, ((q = p.next) != null) ? q : p);
        return item;
      }
      else if ((q = p.next) == null) { // 说明元素为空
        updateHead(h, p);
        return null;
      }
      else if (p == q)
        continue restartFromHead;
      else
        p = q;
    }
  }
}

 

4. 生产者消费者使用ConcurrentLinkedQueue

import java.util.concurrent.ConcurrentLinkedQueue;

public class ProducerAndConsumer {
  private static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();


  static class Producer extends Thread {
    String name;

    public Producer(String name) {
      this.name = name;
    }

    public void run() {
      for (int i = 0; i < 10; i++) {
        queue.offer(i);
        System.out.println(name + " : " + i);
        try {
          Thread.sleep(1 * 1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }


  static class Consumer extends Thread {
    String name;

    public Consumer(String name) {
      this.name = name;
    }

    public void run() {
      for (;;) {
        Object item = queue.poll();
        System.out.println(name + " : " + item);
        try {
          Thread.sleep(1 * 1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }

  public static void main(String[] args) {
    new Producer("p1").start();
    new Producer("p2").start();
    new Consumer("c1").start();
    // new Consumer("c2").start();
  }
}

  

 

目录
相关文章
|
5月前
|
存储 算法 安全
ConcurrentLinkedQueue 的实现原理分析
ConcurrentLinkedQueue 的实现原理分析
24 0
|
6月前
|
存储 负载均衡 安全
Java并发基础:ArrayBlockingQueue全面解析!
ArrayBlockingQueue类是一个高效、线程安全的队列实现,它基于数组,提供了快速的元素访问,并支持多线程间的同步操作,作为有界队列,它能有效防止内存溢出,并通过阻塞机制平衡生产者和消费者的速度差异,它还提供了公平性和非公平性策略,满足不同场景下的需求。
134 1
Java并发基础:ArrayBlockingQueue全面解析!
|
2月前
|
存储 安全 算法
JUC集合: ConcurrentLinkedQueue详解
与此同时,它的无界特性在使用时需要注意,因为过多的数据累积可能会导致内存消耗过大。合理应用 `ConcurrentLinkedQueue` 不仅可以提升应用性能,还能提高程序在并发环境下的可靠性。在实际的开发过程中,合理选择适当的并发容器对于构建高效稳定的系统至关重要。
44 2
|
3月前
ArrayBlockingQueue原理
文章主要介绍了ArrayBlockingQueue的工作原理。ArrayBlockingQueue通过ReentrantLock和Condition实现了高效的阻塞队列,能够有效地避免CPU资源浪费。它非常适合用于生产者-消费者模型的应用场景,特别是需要控制生产者和消费者线程同步的场合。
|
6月前
|
存储 安全 Java
Java并发基础:PriorityBlockingQueue全面解析!
PriorityBlockingQueue类能高效处理优先级任务,确保高优先级任务优先执行,它内部基于优先级堆实现,保证了元素的有序性,同时,作为BlockingQueue接口的实现,它提供了线程安全的队列操作,适用于多线程环境下的任务调度与资源管理,简洁而强大的API使得开发者能轻松应对复杂的并发场景。
172 3
Java并发基础:PriorityBlockingQueue全面解析!
|
6月前
|
存储 缓存 Java
Java并发基础:DelayQueue全面解析!
DelayQueue类专为处理延迟任务设计,它允许开发者将任务与指定的延迟时间关联,并在任务到期时自动处理,从而避免了不必要的轮询和资源浪费,此外,DelayQueue内部基于优先队列实现,确保最先到期的任务总是优先被处理,使得任务调度更为高效和精准。
177 1
Java并发基础:DelayQueue全面解析!
|
6月前
|
缓存 安全 Java
Java并发基础:SynchronousQueue全面解析!
SynchronousQueue的优点在于其直接性和高效性,它实现了线程间的即时数据交换,无需中间缓存,确保了数据传输的实时性和准确性,同时,其灵活的阻塞机制使得线程同步变得简单而直观,适用于需要精确协调的生产者-消费者模型。
160 0
Java并发基础:SynchronousQueue全面解析!
|
6月前
|
存储 监控 安全
Java并发基础:LinkedBlockingQueue全面解析!
LinkedBlockingQueue类是以链表结构实现高效线程安全队列,具有出色的并发性能、灵活的阻塞与非阻塞操作,以及适用于生产者和消费者模式的能力,此外,LinkedBlockingQueue还具有高度的可伸缩性,能够在多线程环境中有效管理数据共享,是提升程序并发性能和稳定性的关键组件。
161 0
Java并发基础:LinkedBlockingQueue全面解析!
|
算法 安全 Java
JUC第十七讲:JUC集合: ConcurrentLinkedQueue详解
JUC第十七讲:JUC集合: ConcurrentLinkedQueue详解
103 0
|
存储 安全 Java
LinkedBlockingQueue 原理
LinkedBlockingQueue 原理