Java并发编程(十五)SynchronousQueue

114 阅读5分钟

1 SynchronousQueue的介绍与应用

SynchronousQueue队列虽然实现了BlockingQueue接口,但它并不是传统意义上的队列,SynchronousQueue的长度是0,内部不存储元素,使用内部类Transferer定义了数据的传输格式,在一个生产者调用put方法存数据到SynchronousQueue后,必须等待消费者调用take拿走这个数据才可以。反之亦然。

  • 简单使用
public static void main(String[] args) throws InterruptedException {
    SynchronousQueue<User> queue = new SynchronousQueue<>();
    new  Thread(() -> {
        User user = new User();
        user.setName("jack&rose");
        System.out.println("publisher:" + user);
        try {
            queue.put(user);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
    new Thread(() -> {
        try {
            Object user = queue.take();
            System.out.println("consumer:" + user);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

2 SynchronousQueue源码分析

2.1 Transferer<E>

Transferer<E>是SynchronousQueue内部的一个抽象类

abstract static class Transferer<E> {
    // transfer抽象方法,定义了数据的传输方式
    // timed表示本次传输是否需要等待,nanos表示要等待的时间(纳秒)
    // 生产者调用transfer方法,需要传递第一个参数,也就是数据
    // 消费者调用transfer方法,第一个参数传递为null,代表获取数据
    abstract E transfer(E e, boolean timed, long nanos);
}

Transferer有两个实现,分别对应了SynchronousQueue的公平操作和不公平操作

  • TransferQueue代表公平处理方式
  • TransferStack代表不公平处理方式

2.2 构造器

SynchronousQueue内部提供公平和非公平两种Transferer的实现,通过构造器确定使用哪一种,默认使用非公平的TransferStack

public SynchronousQueue() {  
    this(false);  
}

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

2.3 TransferQueue源码分析

TransferQueue和TransferStack源码非常相似

  • TransferStack是后进来的线程放到了头部,会先进行匹配
  • TransferQueue匹配的时候都是拿的最前面进来的节点返回数据

这里只看TransferQueue的源码,理解transfer即可

2.3.1 QNode

static final class QNode {
    // 当前节点可以获取到next节点
    volatile QNode next;  
    // item在不同情况下效果不同
    // 生产者:有数据
    // 消费者:为null
    volatile Object item;   
    // 当前线程
    volatile Thread waiter;   
    // 当前属性是永磊区分消费者和生产者的属性
    final boolean isData;
    // 最终生产者需要将item交给消费者
    // 最终消费者需要获取生产者的item
  
    // 省略了大量提供的CAS操作
    ....
}

2.3.2 transfer方法实现

// 当前方法是TransferQueue的核心内容
// e:传递的数据
// timed:false,代表无限阻塞,true,代表阻塞nacos时间
E transfer(E e, boolean timed, long nanos) {
    // 当前QNode是要封装当前生产者或者消费者的信息
    QNode s = null; 
    // isData == true:代表是生产者
    // isData == false:代表是消费者
    boolean isData = (e != null);
    // 死循环
    for (;;) {
        // 获取尾节点和头结点
        QNode t = tail;
        QNode h = head;
        // 为了避免TransferQueue还没有初始化,这边做一个健壮性判断
        if (t == null || h == null)   
            continue;  

        // 如果满足h == t 条件,说明当前队列没有生产者或者消费者,为空
        // 如果有节点,同时当前节点和队列节点属于同一种角色。
        // if中的逻辑是进到队列
        if (h == t || t.isData == isData) { 
            // ===================在判断并发问题==========================
            // 拿到尾节点的next
            QNode tn = t.next;
            // 如果t不为尾节点,进来说明有其他线程并发修改了tail
            if (t != tail)   
                // 重新走for循环   
                continue;
            // tn如果为不null,说明前面有线程并发,添加了一个节点
            if (tn != null) {  
                // 直接帮助那个并发线程修改tail的指向   
                advanceTail(t, tn);
                // 重新走for循环   
                continue;
            }
            // 获取当前线程是否可以阻塞
            // 如果timed为true,并且阻塞的时间小于等于0
            // 不需要匹配,直接告辞!!!
            if (timed && nanos <= 0)   
                return null;
            // 如果可以阻塞,将当前需要插入到队列的QNode构建出来
            if (s == null)
                s = new QNode(e, isData);
            // 基于CAS操作,将tail节点的next设置为当前线程
            if (!t.casNext(null, s))   
                // 如果进到if,说明修改失败,重新执行for循环修改   
                continue;
            // CAS操作成功,直接替换tail的指向
            advanceTail(t, s);   
            // 如果进到队列中了,挂起线程,要么等生产者,要么等消费者。
            // x是返回替换后的数据
            Object x = awaitFulfill(s, e, timed, nanos);
            // 如果元素和节点相等,说明节点取消了
            if (x == s) {  
                // 清空当前节点,将上一个节点的next指向当前节点的next,直接告辞   
                clean(t, s);
                return null;
            }
            // 判断当前节点是否还在队列中
            if (!s.isOffList()) {   
                // 将当前节点设置为head
                advanceHead(t, s);   
                // 如果 x != null, 如果拿到了数据,说明我是消费者
                if (x != null)   
                    // 将当前节点的item设置为自己   
                    s.item = s;
                // 线程置位null
                s.waiter = null;
            }
            // 返回数据
            return (x != null) ? (E)x : e;
        } 
        // 匹配队列中的橘色
        else {   
            // 拿到head的next,作为要匹配的节点   
            QNode m = h.next;  
            // 做并发判断,如果头节点,尾节点,或者head.next发生了变化,这边要重新走for循环
            if (t != tail || m == null || h != head)
                continue;  
            // 没并发问题,可以拿数据
            // 拿到m节点的item作为x。
            Object x = m.item;
            // 如果isData == (x != null)满足,说明当前出现了并发问题,避免并发消费出现坑
            if (isData == (x != null) ||  
                // 如果排队的节点取消,就会讲当前QNode中的item指向QNode
                x == m ||   
                // 如果前面两个都没满足,可以交换数据了。 
                // 如果交换失败,说明有并发问题,
                !m.casItem(x, e)) {   
                // 重新设置head节点,并且再走一次循环  
                advanceHead(h, m);  
                continue;
            }
            // 替换head
            advanceHead(h, m);  
            // 唤醒head.next中的线程
            LockSupport.unpark(m.waiter);
            // 这边匹配好了,数据也交换了,直接返回
            // 如果 x != null,说明队列中是生产者,当前是消费者,这边直接返回x具体数据
            // 反之,队列中是消费者,当前是生产者,直接返回自己的数据
            return (x != null) ? (E)x : e;
        }
    }
}

2.3.3 tansfer方法流程图

image.png