JUC---并发队列源码解析(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源码解析(JDK13)
JUC—ThreadPoolExecutor线程池源码解析(JDK13)
JUC—各种锁(JDK13)
JUC—原子类Atomic*.java源码解析(JDK13)
JUC—CAS源码解析(JDK13)
JUC—ConcurrentHashMap源码解析(JDK13)
JUC—CopyOnWriteArrayList源码解析(JDK13)
JUC—并发队列源码解析(JDK13)
JUC—多线程下控制并发流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


一、并发队列

先看全家福

在这里插入图片描述
并发队列又分为阻塞队列与非阻塞队列

  • 实现了BlockingQueue的就是阻塞队列,最下层左边5个。队列满的时候放不进去,队列空的时候null都取不出来,会阻塞。
  • 最右边2个就是非阻塞队列。

以* Deque结尾的是双端队列,头和尾都能添加和删除。双进双出。一般使用*Queue结尾的。Queue只能一段进一端出。


二、阻塞并发队列

通常,应用于生产者消费者模型。阻塞队列的一段给生产者用,一段给消费者用。阻塞队列是线程安全的,所以生产者消费者都可以是多线程的。

在这里插入图片描述

方法

  • take():获取并移除队列头结点,如果队列没有数据,则阻塞,直到队列里有数据
    在这里插入图片描述
  • put():插入数据,队列满了的话,则阻塞,直到队列有空闲空间
    在这里插入图片描述
  • add():插入数据,队列满了的话,会抛出异常
  • remove():删除数据,队列为空的话,会抛出异常
  • element():返回队列头元素,队列为空的话,会抛出异常
  • offer():添加一个元素,队列满了的话,会返回false
  • poll():取一个元素,队列为空的话,会返回null。取出的同时会删除
  • peak():同poll一样,不过取出时不会删除
1、ArrayBlockingQueue
  • 有界的
  • 初始化需要指定容量
  • 公平:指定公平的话,等待最长时间的线程会优先处理

代码实例在我的仓库:https://github.com/MistraR/springboot-advance 包:com.advance.mistra.test.juc.queue

put方法

public void put(E e) throws InterruptedException {
	// 判空
    Objects.requireNonNull(e);
    final ReentrantLock lock = this.lock;
    // 加锁,等待过程中可以被中断
    lock.lockInterruptibly();
    try {
        while (count == items.length)
        	// 如果队列满了,则阻塞等待。这个nofFull是在初始化时生成的Condition对象
            notFull.await();
        // 队列没满,则入队
        enqueue(e);
    } finally {
    	// 解锁
        lock.unlock();
    }
}
// ArrayBlockingQueue的部分属性
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
2、LinkedBlockingQueue
  • 无界的,最大为Integer.MAX_VALUE
  • 结构:Node包装元素,有两把锁,takeLock和putLock

LinkedBlockingQueue

// 部分初始化参数
// 最大容量
private final int capacity;
// 原子类存储当前队列大小
private final AtomicInteger count = new AtomicInteger();

// 有2把锁,put和take互不干扰
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

// 内部类Node
static class Node<E> {
  E item;
  
  Node<E> next;

  Node(E x) { item = x; }
}

put方法

 public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
     final int c;
     final Node<E> node = new Node<E>(e);
     final ReentrantLock putLock = this.putLock;
     final AtomicInteger count = this.count;
     // 使用的put锁
     putLock.lockInterruptibly();
     try {
         /*
          * Note that count is used in wait guard even though it is
          * not protected by lock. This works because count can
          * only decrease at this point (all other puts are shut
          * out by lock), and we (or some other waiting put) are
          * signalled if it ever changes from capacity. Similarly
          * for all other uses of count in other wait guards.
          */
         while (count.get() == capacity) {
         	 // 如果队列满了,则阻塞
             notFull.await();
         }
         // 队列没满,则入队
         enqueue(node);
         // count+1
         c = count.getAndIncrement();
         if (c + 1 < capacity)
         	 // 当前容量还没有满,则唤醒一个在等待的put线程
             notFull.signal();
     } finally {
     	 // 释放put锁
         putLock.unlock();
     }
     if (c == 0)
         signalNotEmpty();
 }
3、PriorityBlockingQueue
  • 无界的
  • 支持优先级,不是先进先出
  • 自然排序的,插入的元素必须是可比较的
4、SynchronousQueue
  • 容量为0,不存储数据,只做直接交换
  • 是Executors.newCachedThreadPool()使用的阻塞队列

在这里插入图片描述

5、DelayQueue
  • 无界的
  • 根据延迟时间排序
  • 元素必须实现Delayed接口,规定排序规则

三、非阻塞并发队列

1、ConcurrentLinkedQueue

使用链表的结构,使用CAS非阻塞算法来保证线程安全。跟阻塞队列用ReentrantLock保证并发安全不同。

offer方法

public boolean offer(E e) {
final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
	// for死循环
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            // p is last node
            // p是尾节点,newNode就是Node保证之后的元素。
            // 直接CAS设置尾节点为newNode,设置失败则循环,直到CAS成功
            if (NEXT.compareAndSet(p, null, newNode)) {
                // Successful CAS is the linearization point
                // for e to become an element of this queue,
                // and for newNode to become "live".
                if (p != t) // hop two nodes at a time; failure is OK
                    TAIL.weakCompareAndSet(this, t, newNode);
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        else if (p == q)
            // We have fallen off list.  If tail is unchanged, it
            // will also be off-list, in which case we need to
            // jump to head, from which all live nodes are always
            // reachable.  Else the new tail is a better bet.
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

  • 我的公众号:Coding抠腚
  • 偶尔发发自己最近学到的干货。学习路线,经验,技术分享。技术问题交流探讨。
    Coding抠腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章