java并发编程:阻塞队列-SynchronousQueue

生活

Don’t worry if it doesn’t work right. If everything did, you’d be out of a job. (Mosher’s Law of Software Engineering)

不要担心它能否正常工作。如果一切正常,那么你就会失去工作。

前言

昨天一天研究了ArrayBlockingQueue和LinkedBlockingQueue,实现原理都是通过AQS的ReentrantLock来实现的。
今天看了无锁且没有容量没有队列的这个变态的锁:SynchronousQueue。
他的特性就是没有容器,也没有锁:
存数据线程在到达队列时,若发现没有取数据的线程,就在那里一直等待,一直等待有取数据的线程过来唤醒,取走数据,才会释放。反之亦然。
整个实现都是通过CAS实现。
他的源码真的是非常的不好阅读,主要本人涉及到栈、链表的结构的操作不是很熟悉,对照着其他大牛的分析,一步步走Debug来研读,大致摸清了思路。
下面来分析一下源码

SynchronousQueue的创建

先来看下构造器

// 默认创建非公平
 public SynchronousQueue() {
        this(false);
    }
    //指定fair为true创建公平
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue() : new TransferStack();
    }
//公平 TransferQueue 队列 先入先出
//不公平 TransferStack 栈结构 后入先出

这里的先入先出,指的不是队列中的元素先入先出(毕竟这个对了的容量为0),指的是 有两个存数据线程等待,进来一个取数据线程来取数据时,取得是第一个等待的线程,这个叫公平,先入先出。

TransferQueue 中的节点对象:

static final class QNode {
            volatile QNode next;        //下一个节点
            volatile Object item;         //数据
            volatile Thread waiter;       // 当前等待的线程
            final boolean isData; //是存还是取  isData=true 存
.......
        }

TransferStack的节点对象:

 static final int REQUEST    = 0;
        static final int DATA       = 1;
        static final int FULFILLING = 2;
 static final class SNode {
            volatile SNode next;        // 下一个节点
            volatile SNode match;       //匹配的节点
            volatile Thread waiter;     // 等待的线程
            Object item;                // 数据
            int mode;  //模式 REQUEST 消费者 DATA  生产者 FULFILLING 匹配模式
            }

公平模式源码解析

存取共用一套代码,设计的很巧妙

Object transfer(Object e, boolean timed, long nanos) {
            QNode s = null; // constructed/reused as needed
  
            //判断是存还是取
            boolean isData = (e != null);

            for (;;) {
            //拿到头结点和尾节点 
            //这两个节点在创建队列时已经初始化,且item=null isData=false,t=h
                QNode t = tail;
                QNode h = head;
                // 没有初始化就重来
                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);
                      // CAS设置刚才取到的尾节点的下一个节点为这个新节点,失败就自旋
                    if (!t.casNext(null, s))        // failed to link in
                        continue;
                    //更新尾节点为当前的s节点
                    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;
                    }

			//如果s没有离队 
                    if (!s.isOffList()) {           // not already unlinked
                    //就设置它为头结点
                        advanceHead(t, s);          // unlink if head
                        //并且设置s.item=s 
                        if (x != null)              // and forget fields
                            s.item = s;
                        s.waiter = null;
                    }
                    return (x != null) ? 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;
                    //如果状态一直说明已经被匹配了
                    //如果取出来的数据=m说明 取消了
                    //cas设置失败的话 都continue
                    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) ? x : e;
                }
            }
        }

下面来看下具体匹配的方法

Object awaitFulfill(QNode s, Object e, boolean timed, long nanos) {
          //是否设置超时
            long lastTime = timed ? System.nanoTime() : 0;
            //取到当前线程对象
            Thread w = Thread.currentThread();
// 自旋次数
            int spins = ((head.next == s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (;;) {
            //被中断就取消,取消就返回自身节点,否则返回数据
                if (w.isInterrupted())
                    s.tryCancel(e);
                Object x = s.item;
                if (x != e)
                    return x;
                if (timed) {
                    long now = System.nanoTime();
                    nanos -= now - lastTime;
                    lastTime = now;
                    //超时取消
                    if (nanos <= 0) {
                        s.tryCancel(e);
                        continue;
                    }
                }
                //自旋
                if (spins > 0)
                    --spins;
                else if (s.waiter == null)
                    s.waiter = w;
                else if (!timed)
                    LockSupport.park(this);
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }

看了半天才看懂作者的思路,下面来简单的总结一下:
存数据:
如果当前队列为空或者有存数据线程的节点,就尝试入队,并自旋一定次数后进入等待,直接被另一个取数据线程唤醒,取走数据,并清空存数据的节点,设置为头结点。

如果当前队列中有取数据线程的节点就无需入队,通过CAS设置这个取数据线程节点对象的item,并唤醒这个线程。

反之亦然。
以上只讲解了我认为比较重要的关键方法,其他方法都比较简单,可以自己去看下。

非公平模式源码解析

 Object transfer(Object e, boolean timed, long nanos) {
  

            SNode s = null; // constructed/reused as needed
            //判断是生产还是消费,如果传入的e是空的就是消费,反之是生产
            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;
                        }
                        //成功就是把头结点pop出去
                        if ((h = head) != null && h.next == s)
                            casHead(h, s.next);     // help s's fulfiller
                        return (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
                        //CAS 设置新节点 以匹配模式入栈
                    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;
                            //m跟s匹配成功,即刚才入栈的匹配模式的节点与之前的头结点匹配成功,就把这两个都出栈
                            if (m.tryMatch(s)) {
                                casHead(s, mn);     // pop both s and m
                                return (mode == REQUEST) ? m.item : s.item;
                            } else       
                            //否则往后匹配           // lost match
                                s.casNext(m, mn);   // help unlink
                        }
                    }
                } else {
                //如果是匹配模式                            // help a fulfiller
                    SNode m = h.next;    
                    //如果下一个节点是null,就pop出去           // 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
                    }
                }
            }
        }

再来看下 具体匹配的方法:

SNode awaitFulfill(SNode s, boolean timed, long nanos) {
         
         //超时
            long lastTime = timed ? System.nanoTime() : 0;
            //取到当前线程
            Thread w = Thread.currentThread();
            //取到头结点。。这个没用到。。是不是大牛写代码 手抖写了上去,,其实就是s。。所以没必要
            SNode h = head;
            //自旋次数
            int spins = (shouldSpin(s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (;;) {
            //被打断就取消
                if (w.isInterrupted())
                    s.tryCancel();
                    //取到匹配的节点就返回
                SNode m = s.match;
                if (m != null)
                    return m;
                    //超时
                if (timed) {
                    long now = System.nanoTime();
                    nanos -= now - lastTime;
                    lastTime = now;
                    if (nanos <= 0) {
                        s.tryCancel();
                        continue;
                    }
                }
                自旋
                if (spins > 0)
                    spins = shouldSpin(s) ? (spins-1) : 0;
                else if (s.waiter == null)
                    s.waiter = w; // establish waiter so can park next iter
                else if (!timed)
                    LockSupport.park(this);
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }

简单总结一下:
存数据:
如果当前等待栈为空或者前面的节点的模式也是存,就尝试入栈,自旋一定次数进入等待状态,直到被唤醒,唤醒匹配成功后把自己和匹配的节点都出栈。
如果头结点的模式是取,就判断当前节点是不是匹配模式,如果不是,就封装为匹配模式,尝试匹配,匹配成功,两个节点都出栈。

猜你喜欢

转载自blog.csdn.net/qq_28605513/article/details/84673296