BlockingQueue

1.ArrayBlockingQueue

·属性

 /** The queued items */
 final Object[] items;
 ​
 /** items index for next take, poll, peek or remove */
 int takeIndex;
 ​
 /** items index for next put, offer, or add */
 int putIndex;
 ​
 /** Number of elements in the queue */
 int count;
 ​
 /** Main lock guarding all access */
 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();
 }

可以看出来该类是使用ReentrantLock和两个条件队列来保证多线程安全的。并且只有一把锁,也就意味着同一时刻只能有一个线程进来操作。

结构如下:

·put()

相当于生产者

 public void put(E e) throws InterruptedException {
     checkNotNull(e); //插入的元素不能为null
     final ReentrantLock lock = this.lock;
     lock.lockInterruptibly();//先获取可中断锁
     try {
         while (count == items.length) //如果数组满了,那么不能进行放的操作,去条件队列等待
             notFull.await();
         enqueue(e); //放到数组中去
     } finally {
         lock.unlock();
     }
 }
 ​
 private void enqueue(E x) {
     // assert lock.getHoldCount() == 1;
     // assert items[putIndex] == null;
     final Object[] items = this.items; //获取数组
     items[putIndex] = x; //放到数组中
     if (++putIndex == items.length) //如果放入该元素后等于数组的容量,那么让put下标指向0,可以看出这里是一个循环数组
         putIndex = 0;
     count++; //数量+1
     notEmpty.signal();//唤醒因为没有元素而阻塞的消费者线程(take线程)
 }

·take()

相当于消费者

 public E take() throws InterruptedException {
     final ReentrantLock lock = this.lock;
     lock.lockInterruptibly(); //拿锁
     try {
         while (count == 0)  //如果数组中没有元素,那么不能拿,去条件队列中等待
             notEmpty.await();
         return dequeue(); //否则返回拿到的值
     } finally {
         lock.unlock();
     }
 }
 ​
 private E dequeue() {
     // assert lock.getHoldCount() == 1;
     // assert items[takeIndex] != null;
     final Object[] items = this.items; //拿到数组
     @SuppressWarnings("unchecked")
     E x = (E) items[takeIndex];//拿到对应下标的值
     items[takeIndex] = null; //已经被拿走了,这个坑位就不再需要保存当前值了
     if (++takeIndex == items.length) //如果已经拿到最后一个了,那么让takeIndex回到数组开始位置
         takeIndex = 0;
     count--; //数量-1
    //...忽略即可
     notFull.signal(); //唤醒因为队列满了而不能放的生产者线程(因为我这里已经消费了,所以你可以生产了)
     return x; //返回拿到的值
 }

·remove()

 public boolean remove(Object o) {
     if (o == null) return false; 
     final Object[] items = this.items;
     final ReentrantLock lock = this.lock;
     lock.lock(); //上锁
     try {
         if (count > 0) { //如果数组中没有元素,那还移除什么东西?直接返回false
             final int putIndex = this.putIndex;
             int i = takeIndex;
             do {
                 if (o.equals(items[i])) { //通过equals来判断是否包含,如果有则移除
                     removeAt(i); //这个方法就不看了,我们可以想想在数组中删除一个元素是如何删除的?如果是最后一个元素那么就不用移动直接删除即可,否则必须要移动,逻辑比较简单,我就不赘述了
                     return true;
                 }
                 if (++i == items.length)//如果到达数组尾部,那么将i从数组开始继续找(因为这是个循环数组,可能出现takeIndex在putIndex的后面)
                     i = 0;
             } while (i != putIndex); //直到追上putIndex为止
         }
         return false;
     } finally {
         lock.unlock();
     }
 }

可以看到基于数组实现的BlockIngQueue是由一把大锁实现的,生产者和消费者只能有一个进行,那么如果生产者生产了两个,这个时候生产者没有释放锁,消费者还是要等着,这样效率是否有点低下?

2.LinkedBlockingQueue

·属性

 static class Node<E> {
     E item; //用于保存值
     Node<E> next; //用于保存下一个节点
 }
 ​
 private final int capacity;
 private final AtomicInteger count = new AtomicInteger();
 transient Node<E> head;
 private transient Node<E> last;
 ​
 private final ReentrantLock takeLock = new ReentrantLock();
 private final Condition notEmpty = takeLock.newCondition();
 ​
 private final ReentrantLock putLock = new ReentrantLock();
 private final Condition notFull = putLock.newCondition();
 ​
 public LinkedBlockingQueue(int capacity) {
     if (capacity <= 0) throw new IllegalArgumentException();
     this.capacity = capacity;
     last = head = new Node<E>(null);
 }

可以看到链表实现的阻塞队列和数组实现的阻塞队列的不同,在这里锁分为了put锁(生产者锁)和take锁(消费者锁),也就意味着消费者和生产者可以并发执行。并且在创建的时候就初始化了伪节点(head和tail),并且伪节点的是不存放数据的(item=null),

初始化的状态如下:

·put()

生产者线程

 public void put(E e) throws InterruptedException {
     if (e == null) throw new NullPointerException();//不能放null的值
     int c = -1; // 临时变量c
     Node<E> node = new Node<E>(e);//创建新的节点
     final ReentrantLock putLock = this.putLock; 
     final AtomicInteger count = this.count;//拿到此时队列中的元素个数
     putLock.lockInterruptibly(); //拿到锁
     try {
         while (count.get() == capacity) {//如果此时队列满了,那么不能放,直接进入条件队列等待
             notFull.await();
         }
         enqueue(node);//否则,放入到队列中(尾插)
         c = count.getAndIncrement();//注意这个方法是返回原来的数量,也就是此时c=原来队列中有多少个元素
         if (c + 1 < capacity) //如果当前线程插入之后还小于容量,那么唤醒我的兄弟(因为队列满了无法插入而去条件队列中阻塞的生产者线程)
             notFull.signal();
     } finally {
         putLock.unlock();
     }
     if (c == 0) //如果放入元素之前队列是空的,那么唤醒因为队列为空而无法take()的消费者线程(因为此时我已经放入了一个元素了)
         signalNotEmpty();
 }
 ​
 private void enqueue(Node<E> node) {
     last = last.next = node; //可以看出来是尾插法
 }

·take()

消费者线程

 public E take() throws InterruptedException {
     E x;
     int c = -1; //临时变量
     final AtomicInteger count = this.count;
     final ReentrantLock takeLock = this.takeLock;
     takeLock.lockInterruptibly(); //获取锁
     try {
         while (count.get() == 0) { //如果此时队列中没有东西可拿,那么在等待队列中去阻塞
             notEmpty.await();
         }
         x = dequeue(); //否则去队列中拿一个元素(头拿)
         c = count.getAndDecrement(); //返回count之前的值
         if (c > 1) //如果之前还有不止一个元素,那么唤醒我的兄弟起来消费(因为队列为空而无法消费去等待队列阻塞的线程)
             notEmpty.signal();
     } finally {
         takeLock.unlock();
     }
     if (c == capacity) //如果在拿元素之前的队列是满的,那么唤醒因为队列满而无法放入元素的生产者线程(因为此时我已经消费了一个元素了)
         signalNotFull();
     return x;
 }
 ​
 private E dequeue() { //可以看到是头拿的
     Node<E> h = head; //拿到头节点
     Node<E> first = h.next; //拿到头节点的后一个节点
     h.next = h; // help GC 将伪节点的next设置为自己
     head = first;//更换头节点
     E x = first.item;//需要返回的值
     first.item = null;//
     return x;
 }

·remove()

 public boolean remove(Object o) {
     if (o == null) return false;
     fullyLock(); //拿到两把锁
     try {
         for (Node<E> trail = head, p = trail.next;
              p != null; //单链表的移除操作,没什么好讲的了(唯一需要注意的点是:如果在移除之前队列是满了,那么肯定最后会有个唤醒因为队列满而不能put()的生产者线程,也即 notFull.signal()操作)
              trail = p, p = p.next) {
             if (o.equals(p.item)) {
                 unlink(p, trail);
                 return true;
             }
         }
         return false;
     } finally {
         fullyUnlock();
     }
 }

·contains

查询队列中是否包含某个元素。可想而知,如果我在这里查找,而生产者在生产,消费者在消费,这明显不符合常理,所以要执行查找方法就必须拿到两把锁才能执行。否则没有意义。

 public boolean contains(Object o) {
     if (o == null) return false; 
     fullyLock(); //拿到两把锁
     try {
         //可以看出时间复杂度是O(n)
         for (Node<E> p = head.next; p != null; p = p.next) //从第二个节点开始找(伪节点不存放数据)
             if (o.equals(p.item))
                 return true;
         return false;
     } finally {
         fullyUnlock();
     }
 }
 ​
 void fullyUnlock() { //拿到生产者和消费者两把锁
     takeLock.unlock();
     putLock.unlock();
 }

思考:

1.如果初始节点只有1个伪节点,那么生产者和消费者同时操作的时候是否会有冲突?

2.生产者和消费者同时操作链表是否存在问题?

·1.不会,因为存在count时,put和take操作在最开始的时候都会判断count值,如果take线程先判断count==0,那么就会去睡眠,然后put线程放了一个元素,并且在最后会判断if(c==0)那么唤醒take线程,所以不会出现问题。

·2.不存在问题,因为put是尾插,take是头拿。所以两个线程互不影响。

3.LinkedBlockingDeque

有单向链表的LinkedBlockingQueue,那么肯定有双向链表的。

·属性

 static final class Node<E> { //可以看出是由双向链表组成的队列
     E item;
     Node<E> prev;
     Node<E> next;
 }
 ​
 transient Node<E> first;
 transient Node<E> last;
 private transient int count;
 private final int capacity;
 final ReentrantLock lock = new ReentrantLock();
 private final Condition notEmpty = lock.newCondition();
 private final Condition notFull = lock.newCondition();
 ​
 //构造方法
 public LinkedBlockingDeque(int capacity) {
     if (capacity <= 0) throw new IllegalArgumentException();
     this.capacity = capacity;
 }
 ​

可以看到该类只有一把锁,也就意味着,只要拿到了这把锁,那么就是接下来的操作就是单线程的了,也就没有并发,所以性能肯定是没有LinkedBlockingQueue好的。接下来看一些源码分析

·putFirst()

从队列头部放入元素

 public void putFirst(E e) throws InterruptedException {
     if (e == null) throw new NullPointerException(); //不允许放null的值
     Node<E> node = new Node<E>(e); //可以看到这里没有伪节点,每个节点都是动态创建的
     final ReentrantLock lock = this.lock;
     lock.lock();  //拿到锁
     try {
         while (!linkFirst(node)) //linkFirst()是真正插入节点的方法,如果返回false(插入失败),那么意味着队列满了,则需要去条件队列中等待
             notFull.await();
     } finally {
         lock.unlock();
     }
 }
 ​
 //linkFirst()
 private boolean linkFirst(Node<E> node) {
     if (count >= capacity) //如果队列满了,返回false
         return false;
     Node<E> f = first; //拿到头节点
     node.next = f;//将头节点设置为要插入节点的next(也即头插法)
     first = node;//修改头节点
     if (last == null) //如果是第一个节点,那么将last也指向该节点
         last = node;
     else
         f.prev = node;//修改原来头节点的前驱节点
     ++count; //数量+1
     notEmpty.signal(); //唤醒因为队列为null无法消费而去阻塞的消费者线程
     return true;
 }

·putLast()

从队列尾部插入元素

逻辑较为简单,当拿到锁以后就变为了操作双向链表了,在双向链表的尾部插入一个节点,这个操作很简单就不再赘述了

 public void putLast(E e) throws InterruptedException {
     if (e == null) throw new NullPointerException();
     Node<E> node = new Node<E>(e);
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
         while (!linkLast(node))
             notFull.await();
     } finally {
         lock.unlock();
     }
 }
 ​
 //
 private boolean linkLast(Node<E> node) {
     // assert lock.isHeldByCurrentThread();
     if (count >= capacity)
         return false;
     Node<E> l = last;
     node.prev = l;
     last = node;
     if (first == null)
         first = node;
     else
         l.next = node;
     ++count;
     notEmpty.signal();
     return true;
 }

·takeFirst()

从队列头部拿元素,上锁后变为双向链表中从头部移除一个节点,不再赘述。

 public E takeFirst() throws InterruptedException {
     final ReentrantLock lock = this.lock;
     lock.lock(); //上锁
     try {
         E x;
         while ( (x = unlinkFirst()) == null)
             notEmpty.await();
         return x;
     } finally {
         lock.unlock();
     }
 }
 ​
 //
 private E unlinkFirst() {
     Node<E> f = first;
     if (f == null)
         return null;
     Node<E> n = f.next;
     E item = f.item;
     f.item = null;
     f.next = f; // help GC
     first = n;
     if (n == null)
         last = null;
     else
         n.prev = null;
     --count;
     notFull.signal();
     return item;
 }

从Node节点的数据结构可以看出,只有一把锁,所以只要拿到锁就变成单线程操作了,就是对双向链表进行增删改查了,逻辑比较简单,就不再赘述了。

4.LinkedTransferQueue

基于链接节点的无界TransferQueue。 这个队列相对于任何给定的生产者订购元素FIFO(先进先出)。 队列的头部是那些已经排队的元素是一些生产者的最长时间。 队列的尾部是那些已经在队列上的元素是一些生产者的最短时间。

·示例

 new Thread(() -> {
     try {
         log.debug("start--");
         Object take = linkedTransferQueue.take(); //如果队列中没有元素,那么会阻塞
         log.debug("{}", take);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 }).start();
 Thread.sleep(5000);
 linkedTransferQueue.transfer(1);//放入一个元素并且唤醒阻塞线程
 }
 ​
 //运行结果
 2024-01-25 10:45:25 [Thread-0] - start-- //可以看出这里被阻塞了
 2024-01-25 10:45:30 [Thread-0] - 1 //拿到了数据
 ​
 从这个简单的示例中可以看出该类实现了生产者消费者模型,也即数据交换模型。

·属性

 public LinkedTransferQueue() { //空的构造函数
 }
 ​
 private static final int FRONT_SPINS   = 1 << 7; =2^7
 private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;  2^6
 static final int SWEEP_THRESHOLD = 32;
 ​
 static final class Node {
     /*
         判断是生产数据的节点还是消费数据的节点,
         false表示消费
         true表示生产
         
     */
     final boolean isData; 
     /*
         数据保存的地方
         对于生产节点,item保存数据
         对于消费节点,item保存从生产节点中获得的数据
     */
     volatile Object item;   
     volatile Node next; //下一个节点
     volatile Thread waiter;  //要被阻塞的线程对象保存在这里,留给唤醒我的线程使用
 ​
     final boolean isMatched() { //判断是否匹配
         Object x = item; //拿到节点的item值
         /*
             如果是生产节点,那么isData=true,初始item为生产的值,当被匹配成功后,item为消费者的null值(数据交换)
             那么此时x==null为true isData为true,所以为true,那么代表着生产者匹配成功了
             对于消费节点,isData=false,匹配成功后,那么item为生产者的值(数据交换),那么此时x!=null也即false,
             isData=false,即false=flase,代表消费者匹配成功。
             那么第一个x==this代表着数据指向自己(这是什么意思?后续再继续分析)
         */
         return (x == this) || ((x == null) == isData);
     }
     transient volatile Node head;
     private transient volatile Node tail;
     //对被取消的节点进行清理失败的票数计数
     private transient volatile int sweepVotes;
     private static final int NOW   = 0; // for untimed poll, tryTransfer
     private static final int ASYNC = 1; // for offer, put, add //异步非阻塞操作
     private static final int SYNC  = 2; // for transfer, take //同步阻塞操作
     private static final int TIMED = 3; // for timed poll, tryTransfer
 }

可以看到该类竟然没有锁,并且也没有伪节点,而是一个单链表,那么意味着生产者和消费者作用在同一条链表上,那么看看到底是如何工作的。

·transfer()

 //生产者放入数据
 public void transfer(E e) throws InterruptedException {
     /*
           e代表要传递的数据
           true代表当前有数据要传递
           SYNC代表同步等待
           0代表没有超时
           说明这是一个生产者线程
     */
    
     if (xfer(e, true, SYNC, 0) != null) {
         Thread.interrupted(); // failure possible only due to interrupt
         throw new InterruptedException();
     }
 }
 ​
 /*
     注意:如果生产者和消费者匹配成功了,那么数据将会互换,也即item的值将会交换(这也是我们想要的结果),
     1.所以下面在判断item!=null ==isData时,对于生产者而言,原来的item=数据,匹配成功后为null,而isData=true,   所以item!=null ==isData为(false == true即如果被匹配了,那么此判断为false,也即生产者被匹配,同理消费者也是如此)
     2.当线程被删除后,如何标识自己要被删除了?只能用一个东西来标识自己,在该类中使用的是item指向自己来标识自己要被删除了,因为无论是生产者还是消费者都要和item打交道
 */
 ​
 //xfer()
 private E xfer(E e, boolean haveData, int how, long nanos) {
     if (haveData && (e == null))
         throw new NullPointerException(); //如果为生产线程,那么数据不能为null
     Node s = null; //临时节点                        // the node to append, if needed
 ​
     retry: //标记,Java中很少使用
     for (;;) {                            // restart on append race
         for (Node h = head, p = h; p != null;) { // 从头节点开始找到并且匹配节点
             boolean isData = p.isData; //拿到当前节点的类型(是生产者还是消费者)
             Object item = p.item; //拿到要交换的数据
             /*
                 item!=p代表自己没被取消
                 (item != null) == isData 生产者item如果没被匹配,那么就不为null,则为true,生产者的isData本来就为true,所以这个判断为true时就代表没被匹配,如果没被匹配就继续往下执行,
             */
             if (item != p && (item != null) == isData) { // unmatched
                 //如果是同一种类型的,那么结束循环
                 if (isData == haveData)   // can't match
                     break;
                 /*
                     p节点和当前节点的类型相反(一个是生产者一个是消费者),那么cas尝试匹配,
                     如果p是消费线程,那么则将p的item置为e(e是生产者的数据),
                     如果p为生产线程,那么此时e为null,则将p的item置为null,代表匹配成功了,
                     如果cas成功,那么需要将p节点断开,继续下面的操作
                 */
                 if (p.casItem(item, e)) { // match
                     //q默认是匹配成功的节点p,并且q不能是头节点
                     for (Node q = p; q != h;) {
                         Node n = q.next;  //取q的后继节点// update by 2 unless singleton
                         /*
                             head == h 头节点没有变化 并且
                             如果q是最后一个节点,那么修改q为head
                             否则修改q的后继节点为头节点,因为是尾插,
                             所以越后面的节点越不容易被取消,下次匹配时也是先匹配头节点
                         */
                         if (head == h && casHead(h, n == null ? q : n)) {
                             //断开next指针,将next指向自己
                             h.forgetNext();
                             break;
                         }                 // advance and retry
                         if ((h = head)   == null ||
                             (q = h.next) == null || !q.isMatched())
                             break;        // unless slack < 2
                     }
                     //唤醒p节点的等待线程
                     LockSupport.unpark(p.waiter);
                     //将值返回即可
                     return LinkedTransferQueue.<E>cast(item);
                 }
             }
             //执行到这里,上面的p节点已经被匹配,去p的后继节点,如果p.next=p(p=n)的话,表明p已经被移除,
             此时将p设置为新的头节点并且继续循环,否则继续尝试匹配p.next
             Node n = p.next;
             p = (p != n) ? n : (h = head); // Use head if p offlist
         }
 ​
         if (how != NOW) {                 // No matches available
             if (s == null)
                 s = new Node(e, haveData); //初始化等待节点
             Node pred = tryAppend(s, haveData);//将节点插入到链表中
             if (pred == null)//插入链表失败,重试
                 continue retry;           // lost race vs opposite mode
             if (how != ASYNC) //准备阻塞
                 return awaitMatch(s, pred, e, (how == TIMED), nanos);
         }
         return e; // not waiting
     }
 }

·tryAppend()

 private Node tryAppend(Node s, boolean haveData) { //haveData代表线程的种类(是生产线程还是消费线程)
         for (Node t = tail, p = t;;) {        // move p to last node and append
             Node n, u;                        // temps for reads of next & tail
             if (p == null && (p = head) == null) {
                 if (casHead(null, s))
                     return s;                 // initialize
             }
             else if (p.cannotPrecede(haveData))
                 return null;                  // lost race vs opposite mode
             else if ((n = p.next) != null)    // not last; keep traversing
                 p = p != t && t != (u = tail) ? (t = u) : // stale tail
                     (p != n) ? n : null;      // restart if off list
             else if (!p.casNext(null, s))
                 p = p.next;                   // re-read on CAS failure
             else {
                 if (p != t) {                 // update if slack now >= 2
                     while ((tail != t || !casTail(t, s)) &&
                            (t = tail)   != null &&
                            (s = t.next) != null && // advance and retry
                            (s = s.next) != null && s != t);
                 }
                 return p;
             }
         }
     }

该方法的总体逻辑如下:

无标题网络拓扑图(4) 枫叶云笔记

·awaitMatch()

看下是如何阻塞线程的

 //s是当前节点 pred是前一个节点 e是要交换的数据
 private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
     final long deadline = timed ? System.nanoTime() + nanos : 0L;
     Thread w = Thread.currentThread();//获取当前执行线程
     int spins = -1; // 自旋次数
     ThreadLocalRandom randomYields = null; // bound if needed
 ​
     for (;;) {
         Object item = s.item; //当前节点的数据
         if (item != e) {   //说明当前节点已经匹配成功                 // matched
             // assert item != s;
             //将item指向自己
             s.forgetContents();           // avoid garbage
             return LinkedTransferQueue.<E>cast(item);
         }
         //如果线程被中断,返回原始值
         //或者超时,则通过cas将当前节点的item设置为自己,注意这里的cas,如果在超时和被匹配中,如果先匹配成功,那么即使超时也不算
         if ((w.isInterrupted() || (timed && nanos <= 0)) &&
             s.casItem(e, s)) {        // cancel
             unsplice(pred, s); //如果是,那么将s节点从链表中移除
             return e;
         }
         //初始化自旋次数
         if (spins < 0) {                  // establish spins at/near front
             if ((spins = spinsFor(pred, s.isData)) > 0)
                 randomYields = ThreadLocalRandom.current();
         }
         
         else if (spins > 0) {             // spin 开始自旋
             --spins;
             if (randomYields.nextInt(CHAINED_SPINS) == 0) //用来释放cpu资源的随机数,自旋优化
                 Thread.yield();           // occasionally yield
         }
         else if (s.waiter == null) { // 设置等待线程
             s.waiter = w;                 // request unpark then recheck
         }
         else if (timed) {
             nanos = deadline - System.nanoTime();
             if (nanos > 0L)
                 LockSupport.parkNanos(this, nanos);
         }
         else { //阻塞
             LockSupport.park(this);
         }
     }
 }

·unsplice()

 final void unsplice(Node pred, Node s) {  //s是当前节点,是需要被移除的节点,pred是前驱节点
     s.forgetContents(); // 将s的item指向自己
     //s不是头节点并且s是pred的后继节点
     if (pred != null && pred != s && pred.next == s) {
         Node n = s.next; //获取s的后继节点
         if (n == null || //后继节点为null ,意味着s是最后一个节点,则直接进入,否则意味着s不是最后一个节点
             //或者 后继节点不等于s, 则CAS将前驱节点的next节点设置为s的next节点,这条语句就能将s节点从链表中移除了
             //并且判断前驱节点是否被匹配。如果没有被匹配,那么直接返回
             (n != s && pred.casNext(s, n) && pred.isMatched())) {
             for (;;) {               // check if at, or could be, head
                 Node h = head; //拿到最新的头节点
                 //如果前驱节点是头节点或者s是头节点或者此时链表为null,直接返回
                 if (h == pred || h == s || h == null)
                     return;          // at head or list empty
                 if (!h.isMatched())//如果头节点没有被匹配,直接结束循环
                     break;
                 Node hn = h.next; //拿到头节点的后续节点
                 if (hn == null) //链表中只有头节点,此时队列为null,直接返回
                     return;          // now empty
                 if (hn != h && casHead(h, hn)) 
                     h.forgetNext();  // advance head
             }
             if (pred.next != pred && s.next != s) { // recheck if offlist
                 for (;;) {           // sweep now if enough votes
                     int v = sweepVotes;
                     if (v < SWEEP_THRESHOLD) {
                         if (casSweepVotes(v, v + 1))
                             break;
                     }
                     else if (casSweepVotes(v, 0)) {
                         sweep();
                         break;
                     }
                 }
             }
         }
     }
 }

5.SynchronousQueue

·简介

A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 你不能peek在同步队列,因为一个元素,当您尝试删除它才存在; 您无法插入元素(使用任何方法),除非另有线程正在尝试删除它; 你不能迭代,因为没有什么可以迭代。 队列的头部是第一个排队的插入线程尝试添加到队列中的元素; 如果没有这样排队的线程,那么没有元素可用于删除,并且poll()将返回null 。 为了其他Collection方法(例如contains )的目的, SynchronousQueue充当空集合。 此队列不允许null元素。

·简单示例:

  SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();
         new Thread(() -> {
             Object ret = null;
             try {
                 log.debug("生产者等待匹配中····");
                 synchronousQueue.put(1);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
         }).start();
         Thread.sleep(3000);
         new Thread(() -> {
             Object ret = null;
             try {
                 log.debug("消费者准备消费····");
                 ret = synchronousQueue.take();
                 log.debug("ret={}", ret);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
         }).start();
 ​
 ​
 //结果:
 2024-01-26 12:19:42 [Thread-0] - 生产者等待匹配中····
 2024-01-26 12:19:45 [Thread-1] - 消费者准备消费····
 2024-01-26 12:19:45 [Thread-0] - 匹配成功
 2024-01-26 12:19:45 [Thread-1] - ret=1
   
 //将上面2个线程互换得出的结果
 2024-01-26 12:19:04 [Thread-0] - 消费者准备消费····
 2024-01-26 12:19:07 [Thread-1] - 生产者等待匹配中····
 2024-01-26 12:19:07 [Thread-1] - 匹配成功
 2024-01-26 12:19:07 [Thread-0] - ret=1
     
 可以看到线程必须匹配才能执行,如果生产者发现没有人等待消费,那么阻塞,同样消费者发现没有人生产,一样阻塞

·构造函数

 public SynchronousQueue() {
     this(false);
 }
 ​
 public SynchronousQueue(boolean fair) {
     transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
 }

可以看到该类的内部有两种实现,公平的和非公平的,默认是非公平的。

·公平:TransferQueue实现的

·非公平:TransferStack实现的

·put(E e)

将指定的元素添加到此队列,等待另一个线程接收它。并且如果没有消费者等待着消费,那么阻塞

 public void put(E e) throws InterruptedException {
     if (e == null) throw new NullPointerException(); //不允许放null值
     if (transferer.transfer(e, false, 0) == null) {  //调用transferer.transfer()
         Thread.interrupted();
         throw new InterruptedException();
     }
 }

·take()

检索并删除此队列的头,等待另一个线程插入它。并且如果没有生产者等待着被取走,那么阻塞

 public E take() throws InterruptedException {
     E e = transferer.transfer(null, false, 0); //可以看到都是调用transferer.transfer()方法
     if (e != null)
         return e;
     Thread.interrupted();
     throw new InterruptedException();
 }

可以看到不管是生产者还是消费者都是调用transferer.transfer(),不过对于生产者而言,其数据不为null,而对于消费者而言其数据为null(这很容易理解)。

·transfer()公平实现

先看看由TransferQueue实现的transfer的工作原理

·属性

 static final class QNode {
     volatile QNode next;          // next node in queue 下一个节点,可以看出是由单链表实现的
     volatile Object item;         // CAS'ed to or from null 数据
     volatile Thread waiter;       // to control park/unpark 等待线程
     final boolean isData; //消费者还是生产者的标识
     QNode(Object item, boolean isData) {
         this.item = item;
         this.isData = isData;
     }
 }
 ​
 transient volatile QNode head;
 transient volatile QNode tail;
 transient volatile QNode cleanMe;
 ​
 TransferQueue() { //可以看到初始化的时候会创建一个伪节点(不存放数据)
     QNode h = new QNode(null, false); // initialize to dummy node.
     head = h;
     tail = h;
 }

 E transfer(E e, boolean timed, long nanos) {
     QNode s = null; // constructed/reused as needed
     boolean isData = (e != null);//e!null说明是生产者,即生产者的IsData=true,消费者为flase
 ​
     for (;;) {
         QNode t = tail;
         QNode h = head; //将头节点和尾节点保存在线程栈上,注意Doug Lea写代码的风格就是保存旧值然后操作旧值,
                         //后续操作再用旧值和新值比较来判断是否产生了并发
         if (t == null || h == null)         // saw uninitialized value
             continue;                       // spin
 ​
         if (h == t || t.isData == isData) { // empty or same-mode
             QNode tn = t.next;
             if (t != tail)                  // inconsistent read
                 continue;
             if (tn != null) {               // lagging tail
                 advanceTail(t, tn);
                 continue;
             }
             if (timed && nanos <= 0)        // can't wait
                 return null;
             if (s == null)
                 s = new QNode(e, isData);
             if (!t.casNext(null, s))        // failed to link in
                 continue;
 ​
             advanceTail(t, s);              // swing tail and wait
             Object x = awaitFulfill(s, e, timed, nanos);
             if (x == s) {                   // wait was cancelled
                 clean(t, s);
                 return null;
             }
 ​
             if (!s.isOffList()) {           // not already unlinked
                 advanceHead(t, s);          // unlink if head
                 if (x != null)              // and forget fields
                     s.item = s;
                 s.waiter = null;
             }
             return (x != null) ? (E)x : e;
 ​
         } else {                            // complementary-mode
             QNode m = h.next;               // node to fulfill
             if (t != tail || m == null || h != head)
                 continue;                   // inconsistent read
 ​
             Object x = m.item;
             if (isData == (x != null) ||    // m already fulfilled
                 x == m ||                   // m cancelled
                 !m.casItem(x, e)) {         // lost CAS
                 advanceHead(h, m);          // dequeue and retry
                 continue;
             }
 ​
             advanceHead(h, m);              // successfully fulfilled
             LockSupport.unpark(m.waiter);
             return (x != null) ? (E)x : e;
         }
     }
 }

·源码分析:

如果图片模糊可以查看该链接:

无标题网络拓扑图(5) 枫叶云笔记

·transfer()非公平实现

由TransferStack实现的

·属性

 static final int REQUEST    = 0; //代表消费者
 static final int DATA       = 1; //代表生产者
 static final int FULFILLING = 2; //代表当前节点处于配对中
 ​
 //判断当前节点是否处于配对中
 static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }
 ​
 //线程节点
  static final class SNode {
      volatile SNode next;        // next node in stack
      volatile SNode match;       // the node matched to this
      volatile Thread waiter;     // to control park/unpark
      //思考一下:为什么这里不需要使用使用volatile修饰?
      Object item;                // data; or null for REQUESTs
      int mode;
  }
  volatile SNode head;

·transfer()

 E transfer(E e, boolean timed, long nanos) {
     /*
              * Basic algorithm is to loop trying one of three actions:
              *
              * 1. If apparently empty or already containing nodes of same
              *    mode, try to push node on stack and wait for a match,
              *    returning it, or null if cancelled.
              *
              * 2. If apparently containing node of complementary mode,
              *    try to push a fulfilling node on to stack, match
              *    with corresponding waiting node, pop both from
              *    stack, and return matched item. The matching or
              *    unlinking might not actually be necessary because of
              *    other threads performing action 3:
              *
              * 3. If top of stack already holds another fulfilling node,
              *    help it out by doing its match and/or pop
              *    operations, and then continue. The code for helping
              *    is essentially the same as for fulfilling, except
              *    that it doesn't return the item.
    */
 ​
     SNode s = null; // constructed/reused as needed
     int mode = (e == null) ? REQUEST : DATA;
 ​
     for (;;) {
         SNode h = head;
         if (h == null || h.mode == mode) {  // empty or same-mode
             if (timed && nanos <= 0) {      // can't wait
                 if (h != null && h.isCancelled())
                     casHead(h, h.next);     // pop cancelled node
                 else
                     return null;
             } else if (casHead(h, s = snode(s, e, h, mode))) {
                 SNode m = awaitFulfill(s, timed, nanos);
                 if (m == s) {               // wait was cancelled
                     clean(s);
                     return null;
                 }
                 if ((h = head) != null && h.next == s)
                     casHead(h, s.next);     // help s's fulfiller
                 return (E) ((mode == REQUEST) ? m.item : s.item);
             }
         } else if (!isFulfilling(h.mode)) { // try to fulfill
             if (h.isCancelled())            // already cancelled
                 casHead(h, h.next);         // pop and retry
             else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                 for (;;) { // loop until matched or waiters disappear
                     SNode m = s.next;       // m is s's match
                     if (m == null) {        // all waiters are gone
                         casHead(s, null);   // pop fulfill node
                         s = null;           // use new node next time
                         break;              // restart main loop
                     }
                     SNode mn = m.next;
                     if (m.tryMatch(s)) {
                         casHead(s, mn);     // pop both s and m
                         return (E) ((mode == REQUEST) ? m.item : s.item);
                     } else                  // lost match
                         s.casNext(m, mn);   // help unlink
                 }
             }
         } else {                            // help a fulfiller
             SNode m = h.next;               // m is h's match
             if (m == null)                  // waiter is gone
                 casHead(h, null);           // pop fulfilling node
             else {
                 SNode mn = m.next;
                 if (m.tryMatch(h))          // help match
                     casHead(h, mn);         // pop both h and m
                 else                        // lost match
                     h.casNext(m, mn);       // help unlink
             }
         }
     }
 }

·源码分析

·小总结:

1.如果栈为空或者栈顶线程和当前线程是相同模式的节点,那么为当前线程生成一个新的节点插入到栈中等待匹配

2.如果栈顶线程和当前线程的模式相反,那么尝试将一个fulfilling节点压入栈中,然后对这个节点进行匹配,匹配成功将这个节点和被匹配成功的节点一起从栈中弹出,当然如果有别的线程此时进来那么会帮助弹出

3.如果栈顶出现了一个正在匹配的节点,那么尝试帮助它完成匹配并且出栈,然后开始自己的匹配

可以看出Stack和Queue的区别了,Stack是栈顶进栈顶匹配,而Queue是队尾进,队头匹配。

6.PriorityBlockingQueue

一个无界的blocking queue使用与PriorityQueue类相同的排序规则,并提供阻塞检索操作。 虽然这个队列在逻辑上是无界的,但由于资源耗尽,尝试的添加可能会失败(导致OutOfMemoryError )。 这个类不允许null元素。 根据natural ordering的优先级队列也不允许插入不可比较的对象(这样做在ClassCastException )。

·简单示例

 priorityBlockingQueue.put(2);
 priorityBlockingQueue.put(1);
 Object ret = priorityBlockingQueue.take();
 System.out.println("ret = " + ret); //ret=1

·属性

 //默认数组的容量
 private static final int DEFAULT_INITIAL_CAPACITY = 11; 
 //最大的容量,想想为什么要-8?(数组对象的头部需要保存数组的长度)
 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 //保存元素的数组
 private transient Object[] queue;
 //队列中元素的个数
 private transient int size;
 //比较器
 private transient Comparator<? super E> comparator;
 //互斥锁
 private final ReentrantLock lock;
 //条件等待,因为是无界的,所以只有空,没有满
 private final Condition notEmpty;
 //等待数组分配时的自旋锁使用的变量
 private transient volatile int allocationSpinLock;
 //构造
 public PriorityBlockingQueue(int initialCapacity,
                              Comparator<? super E> comparator) {
     if (initialCapacity < 1)
         throw new IllegalArgumentException();
     this.lock = new ReentrantLock(); //锁
     this.notEmpty = lock.newCondition(); //条件队列
     this.comparator = comparator; //比较器
     this.queue = new Object[initialCapacity]; //数组实现的队列
 }

·put()

 public void put(E e) {
     offer(e); // never need to block
 }
 ​
 //-put()是调用offer()来完成的
 public boolean offer(E e) {
     if (e == null)
         throw new NullPointerException(); //不允许放入null值
     final ReentrantLock lock = this.lock; 
     lock.lock(); //上锁,以下操作变为单线程操作
     int n, cap;
     Object[] array;
     //队列满了,进行扩容,while()保证直到扩容成功
     while ((n = size) >= (cap = (array = queue).length))
        tryGrow;
     try {
         Comparator<? super E> cmp = comparator;//拿到比较器
         if (cmp == null) //如果比较为null,则通过Comparable来比较
             siftUpComparable(n, e, array);
         else
             siftUpUsingComparator(n, e, array, cmp);
         size = n + 1; //将队列中元素+1
         notEmpty.signal();//唤醒因为没有元素take()而阻塞的线程
     } finally {
         lock.unlock(); //解锁
     }
     return true;
 }

·tryGrow()

 private void tryGrow(Object[] array, int oldCap) {
     lock.unlock(); // 释放锁,不能因为扩容让其它操作无法执行
     Object[] newArray = null; 
     //获取自旋锁并且将其cas为1
     if (allocationSpinLock == 0 &&
         UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                  0, 1)) {
         try {
             //如果原数组的容量小于64,那么新数组的容量为64+2,否则为64+32
             int newCap = oldCap + ((oldCap < 64) ?
                                    (oldCap + 2) : // grow faster if small
                                    (oldCap >> 1));
             //如果扩容后超过最大容量
             if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                 //将原来的数组容量+1,如果原来容量就是最大值,那么+1会符号溢出
                 int minCap = oldCap + 1;
                 if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                     throw new OutOfMemoryError(); //抛出OOM
                 newCap = MAX_ARRAY_SIZE;//将新容量设置为默认最大值
             }
             //新容量大于老容量并且数组没有被修改引用,那么就创建一个新的数组
             if (newCap > oldCap && queue == array)
                 newArray = new Object[newCap];
         } finally {
             allocationSpinLock = 0;//释放自旋锁
         }
     }
     if (newArray == null) // 如果其它线程正在创建新数组,那么释放CPU资源,没必要做无意义的自旋
         Thread.yield();
     lock.lock();//操作原数组时必须上锁
     //在原数组未改变的情况下进行数组复制并且替换原数组
     if (newArray != null && queue == array) {
         queue = newArray;
         System.arraycopy(array, 0, newArray, 0, oldCap);
     }
 }

·take()

 public E take() throws InterruptedException {
     final ReentrantLock lock = this.lock;
     lock.lockInterruptibly();//拿到可中断的锁
     E result;
     try {
         while ( (result = dequeue()) == null) //如果此时队列中没有元素,那么去条件队列中阻塞
             notEmpty.await();
     } finally {
         lock.unlock();
     }
     return result;
 }

该类的实现比较简单,上了锁之后就是单线程操作,对于入队出队的操作就是堆的操作,这里就不再赘述了。

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值