JAVA队列介绍(Queue)——LinkedTransferQueue

LinkedTransferQueue

LinkedTransferQueue 链表实现的无界阻塞队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。其使用一种预占的模式,当消费者获取元素的时候如果队列不为空则直接获取元素,如果队列为空,则生产一个null的元素,然后在这个节点上等待。
当有新的线程添加元素的时候,如果发现有null的元素则,直接唤醒消费的线程取走元素。

java实现原理

public class LinkedTransferQueue<E> extends AbstractQueue<E>
    implements TransferQueue<E>, java.io.Serializable {
    private static final long serialVersionUID = -3223113410248163686L;

    // 返回到Java虚拟机的可用的处理器数量
    private static final boolean MP =
        Runtime.getRuntime().availableProcessors() > 1;

    // 等待节点在阻塞之前自旋的次数 128
    private static final int FRONT_SPINS   = 1 << 7;

    // 当前置节点正在处理,当前节点在阻塞之前的自选次数 64
    private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;

    
    static final int SWEEP_THRESHOLD = 32;

    static final class Node {
        // 如果是生产的数据则为true,如果是消费线程请求的节点则false
        final boolean isData;   // false if this is a request node
        // 数据内容,如果是消费线程生成的元素则为null
        volatile Object item;   // initially non-null if isData; CASed to match
        // 下一个节点
        volatile Node next;
        // 此节点上等待的线程
        volatile Thread waiter; // null until waiting
        
        // CAS方式设置next
        // CAS methods for fields
        final boolean casNext(Node cmp, Node val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

        // CAS方式设置元素
        final boolean casItem(Object cmp, Object val) {
            // assert cmp == null || cmp.getClass() != Node.class;
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        /**
         * Constructs a new node.  Uses relaxed write because item can
         * only be seen after publication via casNext.
         */
        Node(Object item, boolean isData) {
            UNSAFE.putObject(this, itemOffset, item); // relaxed write
            this.isData = isData;
        }

        // 取消下一节点连接,next指向自己
        final void forgetNext() {
            UNSAFE.putObject(this, nextOffset, this);
        }

        // 设置item自连接,waiter为null
        final void forgetContents() {
            UNSAFE.putObject(this, itemOffset, this);
            UNSAFE.putObject(this, waiterOffset, null);
        }

        // 是否被匹配过
        final boolean isMatched() {
            Object x = item;
            return (x == this) || ((x == null) == isData);
        }

        // 是否是一个未匹配的请求节点
        // 主要是根据isData和元素内容判断
        final boolean isUnmatchedRequest() {
            return !isData && item == null;
        }

        final boolean cannotPrecede(boolean haveData) {
            boolean d = isData;
            Object x;
            return d != haveData && (x = item) != this && (x != null) == d;
        }

        // 匹配数据节点
        final boolean tryMatchData() {
            // assert isData;
            Object x = item;
            if (x != null && x != this && casItem(x, null)) {
                LockSupport.unpark(waiter);
                return true;
            }
            return false;
        }

        private static final long serialVersionUID = -3375979862319811754L;

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long itemOffset;
        private static final long nextOffset;
        private static final long waiterOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> k = Node.class;
                itemOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("item"));
                nextOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("next"));
                waiterOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("waiter"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

    // 头部节点
    /** head of the queue; null until first enqueue */
    transient volatile Node head;

    // 尾部节点
    /** tail of the queue; null until first append */
    private transient volatile Node tail;
    
    // 剥离已删除节点的明显失败数
    /** The number of apparent failures to unsplice removed nodes */
    private transient volatile int sweepVotes;
}

其内部类似LinkedBlockingQueue内部维持了Node类型的对象作为队列的元素。不同之处Node除了正常维护数据和元素关系之外,还会维护一个等待线程。这也是为了预占的模式下保存等待的线程而设计的。

基本操作

入队

LinkedTransferQueue入队的方法主要有add、put、offer三种。

    public boolean add(E e) {
        xfer(e, true, ASYNC, 0);
        return true;
    }
    public void put(E e) {
        xfer(e, true, ASYNC, 0);
    }

    public boolean offer(E e, long timeout, TimeUnit unit) {
        xfer(e, true, ASYNC, 0);
        return true;
    }

但是LinkedTransferQueue除了提供传统Queue的入队方法还挺了tryTransfertransfer两种入队方式

    // 如果可能,立即将元素转移给等待的消费者。 
    // 更确切地说,如果存在消费者已经等待接收它(在 take 或 timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则返回 false。
    boolean tryTransfer(E e);

    // 将元素转移给消费者,如果需要的话等待。 
    // 更准确地说,如果存在一个消费者已经等待接收它(在 take 或timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则等待直到元素由消费者接收。
    void transfer(E e) throws InterruptedException;

出队
    public E take() throws InterruptedException {
        E e = xfer(null, false, SYNC, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E e = xfer(null, false, TIMED, unit.toNanos(timeout));
        if (e != null || !Thread.interrupted())
            return e;
        throw new InterruptedException();
    }

    public E poll() {
        return xfer(null, false, NOW, 0);
    }


这里可以看到,几种方法最终都是使用xfer进行入队和出队操作的,区别是主要传递的数据模式不同。这里主要介绍下xfer

xfer

从上面我们可以看到两种数据操作模式对应的参数

    private E xfer(E e, boolean haveData, int how, long nanos) {
        // 当入队操作haveData=true的时候,插入元素不能为空
        if (haveData && (e == null))
            throw new NullPointerException();
        // 插入节点
        Node s = null;                        // the node to append, if needed
        // 设置起始点
        retry:
        for (;;) {                            // restart on append race
            // 这一部分主要任务为 发现是否有需要匹配的消费节点 
            // 从队首开始匹配
            for (Node h = head, p = h; p != null;) { // find & match first node
                boolean isData = p.isData;
                Object item = p.item;
                // 判断节点是否被匹配
                // 当一个节点的元素不为自己
                // (item != null) == isData 主要有两种 
                // item为null,isDate = false标识其为消费线程创建的节点,并且没有被匹配的值
                // item不为null,isData = true标识其为入队操作
                if (item != p && (item != null) == isData) { // unmatched
                    // 是否被操作匹配
                    // 比如入队的时候haveData=true,此时节点为数据节点而操作为入队操作,则不需要下面的匹配工作
                    // 如果此节点为消费节点,而此节点为入队操作,或者此节点为数据节点,此时操作为出队操作,则继续下面操作
                    if (isData == haveData)   // can't match
                        break;
                    // 通过CAS方式设置节点来判断是否可以匹配
                    if (p.casItem(item, e)) { // match
                        for (Node q = p; q != h;) {
                            Node n = q.next;  // update by 2 unless singleton
                            // 更新head为匹配节点的next节点
                            if (head == h && casHead(h, n == null ? q : n)) {
                                // 旧节点自连接
                                h.forgetNext();
                                break;
                            }                 // advance and retry
                            // 头节点为空,当前节点的下一节点为空,或者此节点并未被匹配到则中断循环
                            if ((h = head)   == null ||
                                (q = h.next) == null || !q.isMatched())
                                break;        // unless slack < 2
                        }
                        // 匹配成功,则唤醒阻塞的线程
                        LockSupport.unpark(p.waiter);
                        // 类型转换,返回匹配节点的元素
                        return LinkedTransferQueue.<E>cast(item);
                    }
                }
                // 若节点已经被匹配过了,则向后寻找下一个未被匹配的节点
                Node n = p.next;
                // 当前节点已经断开链接则从head开始寻找
                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);
                // 将新节点s添加到队列尾并返回s的前驱节点
                Node pred = tryAppend(s, haveData);
                // 前驱节点为null,说明有其他线程竞争,并修改了队列,则从retry重新开始
                if (pred == null)
                    continue retry;           // lost race vs opposite mode
                // 不为ASYNC方法,则同步阻塞等待
                if (how != ASYNC)
                    return awaitMatch(s, pred, e, (how == TIMED), nanos);
            }
            return e; // not waiting
        }
    }

xfer

  1. 从head遍历寻找未被匹配的节点,找到一个未被匹配且数据操作模式和本次操作符合的节点
  2. 匹配节点成功就通过CAS 操作将匹配节点的item字段设置为e,若修改失败,则继续向后寻找节点。
  3. 通过CAS操作更新head节点为匹配节点的next节点,旧head节点进行自连接,唤醒匹配节点的等待线程waiter,返回匹配的 item。

node可能存在的状态

Node存在的状态主要两个字段来维护,一个是isData判断是否数据节点还是消费节点,item来表示数据是否被匹配到。

状态描述isDataitem
数据节点-匹配前trueitem
数据节点-匹配后truenull
消费节点-匹配前falsenull
消费节点-匹配后falsethis

可以看到整个方法根据数据模式被设置为入队和出队两模式。主要是根据要插入队列的元素e以及haveData参数值来判断数据

入队操作ehaveDatahownanos
入队操作非nulltrueASYNC0
出队操作-阻塞nullfalseSYNC0
出队操作-非阻塞nullfalseTIMEDunit.toNanos(timeout)
出队操作-超时nullfalseNOW0

how参数的作用

NOW:立即返回,也不会插入节点

SYNC:插入元素到队列的尾部,然后自旋或阻塞当前线程直到节点被匹配或者取消。

ASYNC:插入元素到队列的尾部,不阻塞直接返回。

TIMED:插入元素到队列的尾部,然后自旋或阻塞当前线程直到节点被匹配或者取消或者超时。

tryAppend

LinkedTransferQueue 在没有完成消费节点匹配后,会将元素添加到队列中,此时使用tryAppend的方法

    private Node tryAppend(Node s, boolean haveData) {
        // 从尾部开始进行元素插入
        for (Node t = tail, p = t;;) {        // move p to last node and append
            Node n, u;                        // temps for reads of next & tail
            // 空的队列,则设置节点为head节点返回
            if (p == null && (p = head) == null) {
                if (casHead(null, s))
                    return s;                 // initialize
            }
            // 如果具有给定模式的节点无法附加到此节点,则返回true,因为此节点不匹配并且具有相反的数据模式。
            // 此时为相反模式
            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
            // 尝试CAS设置下一节点失败
            else if (!p.casNext(null, s))
                p = p.next;                   // re-read on CAS failure
            else {
               // 非自连接
                if (p != t) {                 // update if slack now >= 2
                    // (tail != t || !casTail(t, s)  t不为尾部节点,或者 使用t和s设置尾部节点失败
                    // 
                    while ((tail != t || !casTail(t, s)) &&
                           (t = tail)   != null &&
                           (s = t.next) != null && // advance and retry
                           (s = s.next) != null && s != t);
                }
                return p;
            }
        }
    }

awaitMatch

加入队列后,如果how没有设置非阻塞则调用awaitMatch()方法阻塞等待:

    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; // initialized after first item and cancel checks
        ThreadLocalRandom randomYields = null; // bound if needed
        // 循环
        for (;;) {
            Object item = s.item;
            // 当前节点已经被匹配
            if (item != e) {                  // matched
                // assert item != s;
                // 取消节点,并返回匹配值
                s.forgetContents();           // avoid garbage
                return LinkedTransferQueue.<E>cast(item);
            }
            // 线程中断或者超时,则将s的节点item设置为s
            if ((w.isInterrupted() || (timed && nanos <= 0)) &&
                    s.casItem(e, s)) {        // cancel
                // 断开节点
                unsplice(pred, 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;
                // 生成随机数来让出CPU时间
                if (randomYields.nextInt(CHAINED_SPINS) == 0)
                    Thread.yield();           // occasionally yield
            }
            // 将s的waiter设置为当前线程
            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);
            }
        }
    }

操作流程

整个流程可以用下面内容描述

为消费节点
不为消费节点
入队
head节点
判断head节点是否消费节点
交出数据
入队返回
出队
判断head节点是否数据节点
是-则取出数据
不是-则入队阻塞
tail节点

关于LinkedTransferQueue

LinkedTransferQueue使用了类似LinkedBlockingQueue的链表操作,但是并没有使用锁进行操作,整体性能比LinkedBlockingQueue要好。

其使用一种预占的模式,为队列数据获取提供了一种高效的解决方案。


个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值