【从入门到放弃-Java】并发编程-JUC-SynchronousQueue

简介: 前言上文【从入门到放弃-Java】并发编程-JUC-LinkedBlockingQueue,我们介绍了基于链表的有界阻塞队列LinkedBlockingQueue,它是Executors.newFixedThreadPool中workQueue使用的阻塞队列。

前言

上文【从入门到放弃-Java】并发编程-JUC-LinkedBlockingQueue,我们介绍了基于链表的有界阻塞队列LinkedBlockingQueue,它是Executors.newFixedThreadPool中workQueue使用的阻塞队列。
本文我们来学习ExecutorService.newCachedThreadPool中使用的阻塞队列:SynchronousQueue。

SynchronousQueue


如图和LinkedBlockingQueue一样,都是继承了AbstractQueue类,实现了BlockingQueue和Serializable接口

SynchronousQueue

/**
 * Creates a {@code SynchronousQueue} with nonfair access policy.
 */
public SynchronousQueue() {
    this(false);
}

/**
 * Creates a {@code SynchronousQueue} with the specified fairness policy.
 *
 * @param fair if true, waiting threads contend in FIFO order for
 *        access; otherwise the order is unspecified.
 */
public SynchronousQueue(boolean fair) {
    //公平模式下使用队列,实现先进先出,非公平模式下使用栈,先进后出
    transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}

因为SynchronousQueue的put、offer、take、poll方法全是调用了Transferer的transfer方法,我们一起来看下这个transfer到底是何方神圣。

Transferer

/**
 * Shared internal API for dual stacks and queues.
 */
abstract static class Transferer<E> {
    /**
     * Performs a put or take.
     *
     * @param e if non-null, the item to be handed to a consumer;
     *          if null, requests that transfer return an item
     *          offered by producer.
     * @param timed if this operation should timeout
     * @param nanos the timeout, in nanoseconds
     * @return if non-null, the item provided or received; if null,
     *         the operation failed due to timeout or interrupt --
     *         the caller can distinguish which of these occurred
     *         by checking Thread.interrupted.
     */
    abstract E transfer(E e, boolean timed, long nanos);
}

Transferer是一个抽象类,只有一个抽象方法transfer。可以从注释中看到:

  • e是元素根据e是否为null来控制是生产者还是消费者。
  • timed是布尔值,控制是否使用超时机制。
  • nanos是超时时间。

transfer的具体实现有两个,在Transferer的两个实现类:TransferQueue和TransferStack中

TransferQueue::transfer

E transfer(E e, boolean timed, long nanos) {
    /* Basic algorithm is to loop trying to take either of
     * two actions:
     *
     * 1. If queue apparently empty or holding same-mode nodes,
     *    try to add node to queue of waiters, wait to be
     *    fulfilled (or cancelled) and return matching item.
     *
     * 2. If queue apparently contains waiting items, and this
     *    call is of complementary mode, try to fulfill by CAS'ing
     *    item field of waiting node and dequeuing it, and then
     *    returning matching item.
     *
     * In each case, along the way, check for and try to help
     * advance head and tail on behalf of other stalled/slow
     * threads.
     *
     * The loop starts off with a null check guarding against
     * seeing uninitialized head or tail values. This never
     * happens in current SynchronousQueue, but could if
     * callers held non-volatile/final ref to the
     * transferer. The check is here anyway because it places
     * null checks at top of loop, which is usually faster
     * than having them implicitly interspersed.
     */

    QNode s = null; // constructed/reused as needed
    //判断是消费者还是生产者,如果e为null则消费者,e不为null是生产者
    boolean isData = (e != null);

    for (;;) {
        QNode t = tail;
        QNode h = head;
        //tail和head是队列的尾部和头部,是一个item为空的QNode,如果队列被其它线程改动了,则continue重新处理
        if (t == null || h == null)         // saw uninitialized value
            continue;                       // spin

        //如果队列为空,或者处于same-mode模式
        if (h == t || t.isData == isData) { // empty or same-mode
            //如果不是最后一个节点,则继续寻找最后一个有数据的节点
            QNode tn = t.next;
            if (t != tail)                  // inconsistent read
                continue;
            //如果已经tn不为null,则尝试通过CAS把tn置为尾结点,然后重新执行
            if (tn != null) {               // lagging tail
                advanceTail(t, tn);
                continue;
            }
            //如果超时,直接返回null
            if (timed && nanos <= 0L)       // can't wait
                return null;
            //如果新节点还未创建,则创建一个新的QNode来承载元素e
            if (s == null)
                s = new QNode(e, isData);
            //尝试通过CAS将t的下一个节点设置为s,如果设置失败,则说明t的下一个节点已经被添加了元素,则需要从头开始处理
            if (!t.casNext(null, s))        // failed to link in
                continue;

            //尝试将tail设置为新建的节点s
            advanceTail(t, s);              // swing tail and wait
            //加入队列后阻塞,把等待线程设置为当前线程,等待唤醒处理
            Object x = awaitFulfill(s, e, timed, nanos);
            //如果超时中断则删除这个节点并返回null
            if (x == s) {                   // wait was cancelled
                clean(t, s);
                return null;
            }

            //如果不是tail节点,则判断是否是head节点,
            if (!s.isOffList()) {           // not already unlinked
                advanceHead(t, s);          // unlink if head
                //如果返回的x不为null,则设置item为自身
                if (x != null)              // and forget fields
                    s.item = s;
                //把等待线程设置为null
                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;
            //x为null说明已经被消费
            if (isData == (x != null) ||    // m already fulfilled
                x == m ||                   // m cancelled
                //通过cas将首节点设置为e(null)
                !m.casItem(x, e)) {         // lost CAS
                advanceHead(h, m);          // dequeue and retry
                continue;
            }

            advanceHead(h, m);              // successfully fulfilled
            //唤醒节点设置的线程
            LockSupport.unpark(m.waiter);
            //返回获取到的item
            return (x != null) ? (E)x : e;
        }
    }
}
  • 先判断队列是否为空,或者尾结点与当前节点模式相同,则将节点加入队列尾部
  • 等待线程被唤醒(put被take唤醒,take被put唤醒)处理
  • 如果队列不为空,或者尾结点与当前节点模式不相同,则唤醒头部节点,取出数据,并把头部节点移除

TransferStack::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.
     */

    //如果e是null,则是REQUEST模式,不为null则是DATA模式
    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 <= 0L) {     // can't wait
                if (h != null && h.isCancelled())
                    //cas方式移除头部节点
                    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);
                //如果是中断,则清除节点s并返回null
                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);
            }
        //如果节点h没有被处理完
        } 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
            }
        }
    }
}
  • 先判断队列是否为空,或者尾结点与当前节点模式相同,则将节点加入队列头部
  • 等待线程被唤醒(put被take唤醒,take被put唤醒)处理
  • 如果队列不为空,或者尾结点与当前节点模式不相同,则唤醒头部节点,取出数据,并把头部节点移除

总结

SynchronousQueue是一个无空间的队列即不可以通过peek来获取数据或者contain判断数据是否在队列中。
当队列为空时,队列执行take或put操作都会调用transfer,使线程进入阻塞,等待一个与tail节点模式互补(即put等take、take等put)的请求。
如果新请求与队列tail节点的模式相同,则将请求加入队列,模式不同,则可进行消费从队列中移除节点。
TransferStack:非公平的栈模式,先进后出(头进头出)
TransferQueue:公平的队列模式,先进先出(尾进头出)

我的理解:

  • SynchronousQueue不存储数据,只存储请求
  • 当生产或消费请求到达时,如果队列中没有互补的请求,则将会此请求加入队列中,线程进入阻塞 等待互补的请求到达。
  • 若是互补的请求到达时,则唤醒队列中的线程,消费请求使用生产请求中的数据内容。

更多文章

见我的博客:https://nc2era.com

written by AloofJr,转载请注明出处

目录
相关文章
|
5天前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第12天】 在现代软件开发中,多线程编程是提升应用程序性能和响应能力的关键手段之一。特别是在Java语言中,由于其内置的跨平台线程支持,开发者可以轻松地创建和管理线程。然而,随之而来的并发问题也不容小觑。本文将探讨Java并发编程的核心概念,包括线程安全策略、锁机制以及性能优化技巧。通过实例分析与性能比较,我们旨在为读者提供一套既确保线程安全又兼顾性能的编程指导。
|
5天前
|
数据采集 安全 Java
Java并发编程学习12-任务取消(上)
【5月更文挑战第6天】本篇介绍了取消策略、线程中断、中断策略 和 响应中断的内容
30 4
Java并发编程学习12-任务取消(上)
|
1天前
|
Java
Java一分钟之-并发编程:线程间通信(Phaser, CyclicBarrier, Semaphore)
【5月更文挑战第19天】Java并发编程中,Phaser、CyclicBarrier和Semaphore是三种强大的同步工具。Phaser用于阶段性任务协调,支持动态注册;CyclicBarrier允许线程同步执行,适合循环任务;Semaphore控制资源访问线程数,常用于限流和资源池管理。了解其使用场景、常见问题及避免策略,结合代码示例,能有效提升并发程序效率。注意异常处理和资源管理,以防止并发问题。
22 2
|
1天前
|
安全 Java 容器
Java一分钟之-并发编程:线程安全的集合类
【5月更文挑战第19天】Java提供线程安全集合类以解决并发环境中的数据一致性问题。例如,Vector是线程安全但效率低;可以使用Collections.synchronizedXxx将ArrayList或HashMap同步;ConcurrentHashMap是高效线程安全的映射;CopyOnWriteArrayList和CopyOnWriteArraySet适合读多写少场景;LinkedBlockingQueue是生产者-消费者模型中的线程安全队列。注意,过度同步可能影响性能,应尽量减少共享状态并利用并发工具类。
15 2
|
2天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
12 5
|
2天前
|
安全 Java 容器
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第18天】随着多核处理器的普及,并发编程变得越来越重要。Java提供了丰富的并发编程工具,如synchronized关键字、显式锁Lock、原子类、并发容器等。本文将深入探讨Java并发编程的核心概念,包括线程安全、死锁、资源竞争等,并分享一些性能优化的技巧。
|
2天前
|
安全 Java
Java一分钟之-并发编程:原子类(AtomicInteger, AtomicReference)
【5月更文挑战第18天】Java并发编程中的原子类如`AtomicInteger`和`AtomicReference`提供无锁原子操作,适用于高性能并发场景。`AtomicInteger`支持原子整数操作,而`AtomicReference`允许原子更新对象引用。常见问题包括误解原子性、过度依赖原子类以及忽略对象内部状态的并发控制。要避免这些问题,需明确原子操作边界,合理选择同步策略,并精确控制原子更新。示例代码展示了如何使用这两个类。正确理解和使用原子类是构建高效并发程序的关键。
12 1
|
2天前
|
安全 Java 容器
Java一分钟之-并发编程:并发容器(ConcurrentHashMap, CopyOnWriteArrayList)
【5月更文挑战第18天】本文探讨了Java并发编程中的`ConcurrentHashMap`和`CopyOnWriteArrayList`,两者为多线程数据共享提供高效、线程安全的解决方案。`ConcurrentHashMap`采用分段锁策略,而`CopyOnWriteArrayList`适合读多写少的场景。注意,`ConcurrentHashMap`的`forEach`需避免手动同步,且并发修改时可能导致`ConcurrentModificationException`。`CopyOnWriteArrayList`在写操作时会复制数组。理解和正确使用这些特性是优化并发性能的关键。
9 1
|
2天前
|
Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第18天】在Java并发编程中,锁是一种常用的同步机制,用于保护共享资源的访问。然而,不当的锁使用可能导致性能问题和死锁风险。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁分离和读写锁等技术,以提高并发程序的性能和可靠性。
|
3天前
|
Java 编译器
Java 并发编程中的锁优化策略
【5月更文挑战第17天】在 Java 并发编程中,锁是一种常见的同步机制,用于保护共享资源的访问。然而,不当使用锁可能导致性能问题和死锁风险。本文将探讨 Java 中的锁优化策略,包括锁粗化、锁消除、锁降级以及读写锁等技术,以提高并发程序的性能和可靠性。