七、阻塞队列与源码分析(下)

五、延时阻塞队列DelayQueue

1、概述

1、DelayQueue是一个支持延时获取元素的使用优先级队列实现的无界阻塞队列,队列中的元素必须实现Delayed接口和Comparable接口,也就是说DelayQueue里面的元素必须有compareTo()getDelay()方法存在。
2、在存入元素时可以指定多久才能从队列中获取当前元素。通过getDelay获取剩余延迟时间,只有剩余延迟时间为0才能从队列头中获取元素。
3、应用场景:定时任务调度,保存将会执行的任务和执行时间,一旦从队列中获取到任务就开始执行,如TimeQueue就是使用DelayQueue实现的。

2、DelayQueue原理(部分源码)

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {
    //lock用于保证线程安全,生产和消费都需要获取同一个锁,创建对象实例的时候就初始化
    private final transient ReentrantLock lock = new ReentrantLock();
    //DelayQueue内部使用一个PriorityQueue对象来存放数据
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    /**
     * 在队列中存在元素的时候,第一个调用take()方法的线程将成为leader线程,它将会在available上等待队列头结点剩余的延迟时间
     * 其他的线程将会成为follower线程,它们会一直在available上一直等待
     * leader线程苏醒之后会将leader变量置空,在获取到元素之后最后会唤醒一个在available上等待的follower线程
     * 被唤醒的follower线程将可能成为新的leader线程
     */
    private Thread leader = null;
    /**
     * 一个条件变量available,用于消费者线程的等待和唤醒,创建对象实例的时候就初始化
     * 生产线程不会等待,因为队列是“无界”的,可以一直入队。
     */
    private final Condition available = lock.newCondition();
    
    /**
     * 创建一个最初为空的新DelayQueue
     */
    public DelayQueue() {}

    /**
     * 创建一个最初包含Delayed实例的指定集合元素的DelayQueue
     */
    public DelayQueue(Collection<? extends E> c) {
        //直接调用了AbstractQueue的公共addAll方法
        this.addAll(c);
    }
    /**
     * 将指定元素插入此延迟队列。该队列是无界的,所以此方法不会阻塞,一定会返回true
     */
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        //不可中断的等待获取lock锁,即不响应中断
        lock.lock();
        try {
            //调用q.offer方法,非阻塞的将元素加入到PriorityQueue中,并根据compareTo方法比较并构建小顶堆
            //小顶堆的详细原理在PriorityBlockingQueue的文章中已经详细讲解了
            //compareTo方法应该根据延迟时间比较大小,这样才能满足DelayQueue的性质
            q.offer(e);
            //如果调用peek()方法获取的队列头部元素就是e,这表示新加入的元素e就是延迟时间最短的元素
        	//无论此前有没有leader,此时都可以选出新的leader,因为有了延迟更短的元素
            if (q.peek() == e) {
                //leader置为null
                leader = null;
                //唤醒一个在available上等待的消费线程,让它和新消费线程重新争夺leader
                available.signal();
            }
            //如果上面没有抛出异常,那么返回true
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    
    /**
     * 获取并移除此延迟队列已过期的队头,如果此时没有已过期的队头,那么一直等待
     */
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //可中断的等待获取lock锁,即响应中断
        lock.lockInterruptibly();
        try {
            /*开启一个死循环,获取并移除队头元素,即延迟时间最短的元素或者延迟完毕的元素*/
            for (;;) {
                //获取但不移除队列头部元素first
                E first = q.peek();
                //如果队列头元素为null,说明队列为空
                if (first == null)
                    //当前线程在available上无限等待,成为follower线程,直到被唤醒或被中断
                    available.await();
                else {
                    //获取队列头部元素first的剩余延迟时间delay纳秒
                    long delay = first.getDelay(NANOSECONDS);
                    //如果delay小于等于0,说明队头可以出队列
                    if (delay <= 0)
                        //调用q.poll()方法移除并返回队列头部元素,并重构小顶堆,这是出队列操作的唯一正常出口
                        return q.poll();
                    /*
                     * 到这里,说明delay大于0,即还没有到时间,那么将该线程方法栈中的first保留的队列头部不元素引用置空
                     * 因为接下来该线程也将会进入等待,等待完毕之后会重新循环获取队列头部
                     * 如果不置空,可能造成内存泄漏,因为first指向的头结点可能被多个线程内部的first变量同时持有,如果在线程等待之前不将first置空,
                     * 那么如果某个时刻该结点出队列,那么这个结点理应被清除,但是由于还有很多等待的线程在持有该结点对象的引用,
                     * 导致该结点对象不能被即时的清理,造成内存泄漏
                     */
                    first = null; // don't retain ref while waiting
                    /*
                     * 如果leader线程不为null,说明:
                     * 有线程成为了leader线程在进行超时等待了,并且没有新元素入队,或者有新元素但是新元素不是延迟时间最短的元素
                     * 上面的情况说明,有一个leader线程在等待延迟时间最短的元素出队列,那么该线程自然成为follower线程
                     */
                    if (leader != null)
                        //在available上无限等待,直到被唤醒或被中断,随后继续下一次循环
                        available.await();
                    else {
                        /*
                         * 如果leader线程为null,说明说明:
                         * 1、目前没有线程成为了leader线程,可能是前一个leader线程被唤醒了
                         * 2、原本是有leader线程在进行超时等待,但是后来新入队列的元素延迟时间最短,成为了队头,此时leader引用被入队操作置空
                         * 满足上面的情况,那么此时的消费线程成为leader线程
                         */
                        Thread thisThread = Thread.currentThread();//获取当前线程
                        //leader赋值为当前线程
                        leader = thisThread;
                        try {
                            //当前线程thisThread在available上超时等待delay纳秒,直到被唤醒或被中断或超时时间到了自然唤醒
                            //当thisThread正常返回之后,理想情况就是此时目前的队列头部元素first的剩余延迟时间也同时为0
                            //那么该线程进入下一次循环,将会使队头出队
                            available.awaitNanos(delay);
                        } finally {
                            //无论是awaitNanos自然唤醒,还是被中断唤醒,都会执行finally
                            //如果此时的leader还是指向当前线程
                            if (leader == thisThread)
                                //那么leader置空,后续循环有可能重新成为leader线程,也可能成为follower线程
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            //如果leader为null,并且此时队列不为空
            if (leader == null && q.peek() != null)
                //唤醒一个在available上等待的消费线程,让它和新消费线程重新争夺leader
                available.signal();
            //释放锁
            lock.unlock();
        }
    }
    
    /**
     * 获取并移除此延迟队列已过期的头,如果此时队列没有过期的元素,那么直接返回null
     */
    public E poll() {
        final ReentrantLock lock = this.lock;
        //不可中断的等待获取lock锁,即不响应中断
        lock.lock();
        try {
            //获取但不移除队列头部元素first
            E first = q.peek();
            //如果队列为空,或者队头元素的剩余延迟时间大于0,那么直接返回null
            if (first == null || first.getDelay(NANOSECONDS) > 0)
                return null;
            else
                //否则,说明队列不为空并且此时队头元素已经过期,那么调用q.poll获取并移除队头
                return q.poll();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
}

3、使用示例

/**
 * @Date: 2022/6/19
 * 延时阻塞队列
 */
public class DelayQueueTest {
    public static void main(String[] args) throws InterruptedException {
        //创建DelayQueue队列
        DelayQueue<DelayTask> delayQueue = new DelayQueue<>();
        //创建并添加10个任务
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            //每个任务的延迟时间在[0,1000)之间
            DelayTask task = new DelayTask(random.nextInt(1000), "task " + (i + 1));
            delayQueue.add(task);
        }
        //循环获取过期任务并打印,可以看到结果就是延迟时间最短的任务最先出队,这和任务添加的顺序无关
        while (true) {
            System.out.println(delayQueue.take());
            if (delayQueue.isEmpty()) {
                break;
            }
        }
    }

    static class DelayTask implements Delayed {
        private final long delayTime; //延迟时间
        private final long expire; //到期时间
        private String taskName; //任务名称

        /**
         * 构造器
         * @param delayTime 延迟时间,毫秒
         * @param taskName  任务名称
         */
        DelayTask(long delayTime, String taskName) {
            this.delayTime = delayTime;
            //计算出到期时间点 -> 延迟时间 + 当前时间
            this.expire = delayTime + System.currentTimeMillis();
            this.taskName = taskName;
        }

        /**
         * 在调用DelayQueue的出队列的时候会调用该方法并传递参数NANOSECONDS
         * @param unit 时间单位,
         * @return 返回剩余延迟时间纳秒 -> 到期时间-当前时间
         */
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        /**
         * 用于加入队列时元素之间比较构建小顶堆的方法
         * @param o 参数对象
         * @return 如果返回负数,表示this对象小于,那么this对象将比参数对象优先出队列
         */
        @Override
        public int compareTo(Delayed o) {
            //比较this对象和参数对象剩余时间长短
            //this对象剩余时间大于参数对象剩余时间,则返回大于0。那么将会设置this对象的出队列优先级小于当前对象
            //this对象剩余时间小于参数对象剩余时间,则返回小于0。那么将会设置this对象的出队列优先级大于当前对象
            return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
        }
        @Override
        public String toString() {
            return "DelayedEle{" +
                    "delayTime=" + delayTime +
                    ", expire=" + expire +
                    ", taskName='" + taskName + '\'' +
                    '}';
        }
    }
}

六、SynchronousQueue同步队列

1、概述

1、SynchronousQueue是一个不存储元素的阻塞队列,因为它的内部没有容量,每一个入队操作必须等待一个出队操作,否则入队线程将被阻塞;同理每一个出队操作必须等待一个入队操作。两个操作都是同步等待的。
2、SynchronousQueue本身并不存储任何元素,它的作用更像是用来同步两个线程之间的数据传递,比如将一个线程中数据传递给另外一个线程使用,比如线程任务的传递。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。
3、声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为:
  • 公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者。
  • 非公平模式:SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,如果生产者和消费者的处理速度有差距,则很容易出现饥饿的现象(即可能有些生产者或者消费者的数据永远都得不到处理)。
  • 默认采用非公平模式,可能会造成部分线程长期阻塞,但是并发性能更高。

2、SynchronousQueue原理(部分源码)

public class SynchronousQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
    /**
     * 获取的是CPU的实际可用线程数量,比如i7-8750H它具有6核心12线程,因此获取的就是12而不是6
     * 通常CPU的实际可用线程数量越高,运行并发的程序的效率也越高
     */
    static final int NCPUS = Runtime.getRuntime().availableProcessors();

    /**
     * 超时等待的线程在阻塞之前应该自旋的次数
     * 该值是经验推导的——它适用于各种处理器和OS。
     * 从经验上看,最佳值似乎不会随CPU数量(超过2)而变化,因此只是一个常数(0或32)
     */
    static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;

    /**
     * 非超时等待的线程在阻塞之前应该自旋的次数
     * 通常大于超时等待的线程的旋转次数,因为不需要检查每次旋转的之后的剩余超时时间,它们旋转的更快(0或512)
     */
    static final int maxUntimedSpins = maxTimedSpins * 16;

    /**
     * 采用自旋而不是使用park阻塞的超时时间边界纳秒数,这是也是一个估计值。
     * 即超时时间大于1000L,那么使用parkNanos阻塞当前线程,否则采用快速的自旋等待即可
     * 原因在于,非常短的超时等待无法做到十分精确,如果这时再进行超时等待,相反会让nanosTimeout的超时从整体上表现得反而不精确。
     * 因此,在超时非常短的场景下,AQS会进入无条件的快速自旋而不是挂起线程。
     * 这个参数在AQS的超时获取锁,Condition的超时等待中也被使用
     */
    static final long spinForTimeoutThreshold = 1000L;
    
    /**
     * 传输器实例的引用,用于在传输操作无法正常完成时存储阻塞的线程以及元素
 	 * 只会初始化某一个公平或者非公平模式的子类实例
 	 */
    private transient volatile Transferer<E> transferer;
    
    /**
     * 抽象传输器,定义了子类TransferQueue、TransferStack的关键抽象同名方法transfer
     */
    abstract static class Transferer<E> {
        //传输数据,put或者take时都会调用该方法
        abstract E transfer(E e, boolean timed, long nanos);
    }
    
    /**
     * 创建一个具有非公平访问策略的SynchronousQueue
     */
    public SynchronousQueue() {
        this(false);
    }
    
    /**
     * 创建一个具有指定公平策略的SynchronousQueue。
     * fair如果为true,则等待线程以FIFO的顺序竞争访问;否则顺序是不可预期的。
     */
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }
    
    /**
     * 尝试传递指定元素,如果此时另一个线程正在等待接收元素,那么传递成功之后返回,
     * 否则阻塞该线程,直到另一个线程接收这个元素
     */
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        //调用transfer方法,不同的模式调用自己的实现,传递 e、false、0
    	//即表示这是尝试传递数据的非超时操作的生产者请求
        if (transferer.transfer(e, false, 0) == null) {
            //transfer的返回值如果为null,表示因为被中断而返回
        	//那么调用interrupted静态方法重置当前线程的中断状态为false
            Thread.interrupted();
            throw new InterruptedException();
        }
    }
    
    /**
     * 尝试传递指定元素,如果此时另一个线程正在等待接收元素,
     * 那么传递成功之后返回true,否则立即返回false,不会阻塞该线程
     */
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        //调用transfer方法,不同的模式调用自己的实现,传递 e、true、0
        //即表示这是一个尝试传递数据的超时操作的生产者请求,并且超时时间为0纳秒,这实际上就是相当于立即返回的操作
        //返回值不为null,则表示传递成功,返回true,否则返回false
        return transferer.transfer(e, true, 0) != null;
    }
    
    /**
     * 尝试接收元素,如果此时另一个线程正在等待传递元素,那么接收成功之后返回,
     * 否则阻塞该线程,直到另一个传递元素线程的线程来匹配!
     */
    public E take() throws InterruptedException {
        //调用transfer方法,不同的模式调用自己的实现,传递 null、false、0
    	//即表示这是尝试接收数据的非超时操作的消费者请求,获取返回值e
        E e = transferer.transfer(null, false, 0);
        //如果e不为null,那么返回e
        if (e != null)
            return e;
        //的返回值如果为null,表示因为被中断而返回
    	//那么调用interrupted静态方法重置当前线程的中断状态为false
        Thread.interrupted();
        throw new InterruptedException();
    }
    
    /**
     * 尝试接收元素,如果此时另一个线程正在等待传递元素,那么接收成功之后返回接收到的元素,
     * 否则立即返回null,不会阻塞该线程!
     */
    public E poll() {
        return transferer.transfer(null, true, 0);
    }
}

七、链表双向阻塞队列LinkedBlockingDeque

1、概述

1、LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,双向队列指的是可以从队列的两端插入和移除元素。
2、双端队列因为多了一个操作队列的入口,在多线程同时插入元素时,也就减少了一半的竞争。与其他阻塞队列相比,LinkedBlockingDeque多了addFirst、addLast、offerFirst、offerLast、peekFirst、peekLast等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。另外插入方法add等于addList,移除方法remove等效于removeFirst。
3、和LinkedBlockingQueue一样,LinkedBlockingDeque同样作为有界队列,默认元素个数就是最大容量即Integer.MAX_VALUE,也可以指定最大容量。

2、LinkedBlockingDeque原理(部分源码)

public class LinkedBlockingDeque<E> extends AbstractQueue<E> implements BlockingDeque<E>, java.io.Serializable {
    /**
     * 双端队列的结点实现类
     */
    static final class Node<E> {
        /** 值域,如果结点被删除则item为null */
        E item;

        /** 结点的前驱 */
        Node<E> prev;

        /** 结点的后继 */
        Node<E> next;

        /** 构造器 */
        Node(E x) {
            item = x;
        }
    }
    
    /** 头结点,可以为null */
    transient Node<E> first;

    /** 尾结点,可以为null */
    transient Node<E> last;

    /** 队列元素计数器 */
    private transient int count;

    /** 队列的容量,初始化之后就不能变了 */
    private final int capacity;

    /** 生产、消费都需要获取的锁 */
    final ReentrantLock lock = new ReentrantLock();

    /** notEmpty条件对象,当队列为空时用于挂起消费线程 */
    private final Condition notEmpty = lock.newCondition();

    /** notFull条件对象,当队列已满时用于挂起生产线程 */
    private final Condition notFull = lock.newCondition();
    
    /**
     * 创建一个容量为Integer.MAX_VALUE的LinkedBlockingDeque
     */
    public LinkedBlockingDeque() {
        //调用另一个构造器,参数为Integer.MAX_VALUE
        this(Integer.MAX_VALUE);
    }

    /**
     * 创建一个具有指定容量的LinkedBlockingDeque
     */
    public LinkedBlockingDeque(int capacity) {
        //capacity大小的校验
        if (capacity <= 0) throw new IllegalArgumentException();
        //capacity初始化为指定值
        this.capacity = capacity;
    }
    
    /**
     * 创建一个容量是Integer.MAX_VALUE的LinkedBlockingDeque,
     * 包含指定集合的全部元素,元素按该集合迭代器的遍历顺序添加
     */
    public LinkedBlockingDeque(Collection<? extends E> c) {
        //调用另一个构造器,初始化容量为Integer.MAX_VALUE
        this(Integer.MAX_VALUE);
        //和LinkedBlockingQueue是一样的,需要加锁来保证数据的可见性,因为头、尾结点没有使用volatile修饰
        final ReentrantLock lock = this.lock;
        //获取锁
        lock.lock(); // Never contended, but necessary for visibility
        try {
            //遍历指定集合
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                //调用linkLast将指定集合的元素添加到队列尾部
                if (!linkLast(new Node<E>(e)))
                    //如果linkLast返回false,说明集合元素数量达到了最大容量,因此抛出异常
                    throw new IllegalStateException("Deque full");
            }
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    
    /**
     * 将指定的元素插入此双端队列的头部,如果该队列已满,则线程等待
     */
    public void putFirst(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        //新建Node结点
        Node<E> node = new Node<E>(e);
        //不可中断的等待获取生产者锁,即不响应中断
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //循环调用linkFirst尝试将node结点加入队头
            while (!linkFirst(node))
                //如果失败,表示队列满了,那么该线程在notFull条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
                notFull.await();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    
    /**
     * 将指定结点链接到队列头部成为新的头结点
     */
    private boolean linkFirst(Node<E> node) {
        //如果队列满了,那么直接返回false
        if (count >= capacity)
            return false;
        //f变量保存此时的first队头结点,可能为null
        Node<E> f = first;
        //新结点的后继设置为f
        node.next = f;
        //first指向新结点
        first = node;
        //如果last也为null,说明队列为空
        if (last == null)
            //那么last也指向该结点
            last = node;
        else
            //f的前驱指向新结点
            f.prev = node;
        //计数器自增1
        ++count;
        //添加了元素结点之后,唤醒在notEmpty等待的消费线程
        notEmpty.signal();
        return true;
    }
    
    /**
     * 将指定的元素插入此队列的尾部,如果该队列已满,则线程等待
     */
    public void putLast(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        //新建Node结点
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        //不可中断的等待获取生产者锁,即不响应中断
        lock.lock();
        try {
            //循环调用linkLast尝试将node结点加入队尾
            while (!linkLast(node))
                //如果失败,表示队列满了,那么该线程在notFull条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
                notFull.await();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    
    /**
     * 将指定结点链接到队列尾部成为新的尾结点
     */
    private boolean linkLast(Node<E> node) {
        //如果队列满了,那么直接返回false
        if (count >= capacity)
            return false;
        //l变量保存此时的last队尾结点,可能为null
        Node<E> l = last;
        //新结点的前驱设置为l
        node.prev = l;
        //last指向新结点
        last = node;
        //如果first也为null,说明队列为空
        if (first == null)
            //那么first也指向该结点
            first = node;
        else
            //l的后继指向新结点
            l.next = node;
        //计数器自增1
        ++count;
        //添加了元素结点之后,唤醒在notEmpty等待的消费线程
        notEmpty.signal();
        return true;
    }
    
    /**
     * 获取并移除此双端队列的头部元素,如果该队列已空,则线程等待
     */
    public E takeFirst() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //不可中断的等待获取锁,即不响应中断
        lock.lock();
        try {
            E x;
            //循环调用unlinkFirst尝试将头结点出队并返回结点的item值x
            while ( (x = unlinkFirst()) == null)
                //如果x为null,表示队列空了,那么该线程在notEmpty条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
                notEmpty.await();
            return x;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    
    /**
     * 尝试将头结点出队并返回结点的item值x
     */
    private E unlinkFirst() {
        //f变量保存此时的first队头结点,可能为null
        Node<E> f = first;
        //如果f为null,表示队列为空,直接返回null
        if (f == null)
            return null;
        //获取f的后继结点n
        Node<E> n = f.next;
        //获取f结点的item值item
        E item = f.item;
        //f的item置空
        f.item = null;
        //f的后继指向自己,结点出队列
        f.next = f; // help GC
        //first指向f的后继n
        first = n;
        //如果n为nul
        if (n == null)
            //那么last指向null
            last = null;
        else
            //n的前驱置空
            n.prev = null;
        //计数器自减1
        --count;
        //出队成功之后,唤醒在notFull等待的生产线程
        notFull.signal();
        return item;
    }
    
    /**
     * 获取并移除此双端队列的尾部元素,如果该队列已空,则线程等待
     */
    public E takeLast() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //不可中断的等待获取锁,即不响应中断
        lock.lock();
        try {
            E x;
            //循环调用unlinkLast尝试将尾结点出队并返回结点的item值x
            while ( (x = unlinkLast()) == null)
                //如果x为null,表示队列空了,那么该线程在notEmpty条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
                notEmpty.await();
            //x不为null,表示出队成功,结束循环,返回x
            return x;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    
    /**
     * 尝试将尾结点出队并返回结点的item值x
     */
    private E unlinkLast() {
        //l变量保存此时的last队尾结点,可能为null
        Node<E> l = last;
        //如果l为null,表示队列为空,直接返回null
        if (l == null)
            return null;
        //获取l的前驱结点p
        Node<E> p = l.prev;
        //获取l结点的item值item
        E item = l.item;
        //l的item置空
        l.item = null;
        //l的前驱指向自己,结点出队列
        l.prev = l; // help GC
        //last指向l的前驱p
        last = p;
        //如果p为nul
        if (p == null)
            //那么first指向null
            first = null;
        else
            //p的后继置空
            p.next = null;
        //计数器自减1
        --count;
        //出队成功之后,唤醒在notFull等待的生产线程
        notFull.signal();
        return item;
    }
}

3、与LinkedBlockingQueue相比较

1、LinkedBlockingDeque可以看作LinkedList集合的线程安全的实现,可以在队头和队尾对元素做出队和入队操作。LinkedBlockingQueue只能在队尾入队列,在队头出队列。LinkedBlockingDeque相比于LinkedBlockingQueue,可操作的方法和方式更加多样。
2、LinkedBlockingDeque内部只有一个锁,出队、入队、size、迭代等操作都需要获取该锁。而LinkedBlockingQueue则有两把锁,分别对队尾的生产者线程和队头的消费者线程应用不同的锁,因此LinkedBlockingQueue的并发度比LinkedBlockingDeque更高,带来的问题是迭代等需要遍历整个队列的操作需要同时获取两把锁。

八、链表传输队列LinkedTransferQueue

1、概述

1、LinkedTransferQueue是一个由链表结构组成的无界传输阻塞队列。
2、TransferQueue是一个继承了BlockingQueue的接口,并且增加若干新的方法。LinkedTransferQueue是实现类,其定义为一个无界的队列,一样具有先进先出(FIFO)的特性。

2、LinkedTransferQueue原理(部分源码)

public class LinkedTransferQueue<E> extends AbstractQueue<E>
    implements TransferQueue<E>, java.io.Serializable {
    /** CPU可用线程数量是否大于1,在多cpu或者多核处理器上为true */
    private static final boolean MP = Runtime.getRuntime().availableProcessors() > 1;
    
    /**
     * 当前结点作为队列中第一个等待结点的时候,结点在阻塞前的自旋次数
     */
    private static final int FRONT_SPINS   = 1 << 7;
    
    /**
     * 当结点前面是另一个明显正在自旋的结点时,在该结点阻塞之前要自旋的次数FRONT_SPINS的两倍
     */
    private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;
    
    /**
     * 可容忍的尝试从队列移除被删除的结点的失败次数的最大值,
     * 当sweepVotes大于等于SWEEP_THRESHOLD时,将扫描全部队列并移除遇到的被删除的结点
     */
    static final int SWEEP_THRESHOLD = 32;
    
    /**
     * 队列类的头结点引用,可以为null,head指向的结点不一定是队列真正的头结点
     * 具有volatile的特性
     */
    transient volatile Node head;

    /**
     * 队列类的尾结点引用,可以为null,tail指向的结点不一定是队列真正的尾结点
     * 具有volatile的特性
     */
    private transient volatile Node tail;

    /**
     * 尝试从队列移除被删除的结点的失败次数
     * 具有volatile的特性
     */
    private transient volatile int sweepVotes;
    
    //不会阻塞的其他方法,出队 - poll()、remove()、尝试传递 - tryTransfer(e)
    private static final int NOW   = 0; // for untimed poll, tryTransfer
    
    //不会阻塞的入队方法,入队 - offer(e)、put(e)、offer(e, timeout, unit)、add(e)
    private static final int ASYNC = 1; // for offer, put, add
    
    //非超时的阻塞的方法,传递 - transfer()、出队 - take()
    private static final int SYNC  = 2; // for transfer, take
    
    //超时的阻塞的方法,超时传递 - tryTransfer(e, timeout, unit)()、超时出队 - poll(timeout, unit)
    private static final int TIMED = 3; // for timed poll, tryTransfer
    
    /**
     * 结点内部类,内部属性和SynchronousQueue中的TransferQueue内部类是一样的
     */
    static final class Node {
        //是否存放了数据,即是否是request结点,true是,false否,final修饰,初始化之后不可改变
        final boolean isData;
        //数据域
        volatile Object item;
        //后继
        volatile Node next;
        //当前结点所属的线程,用于控制park/unpark
        volatile Thread waiter;

        //尝试CAS的将调用结点的next从cmp指向val
        final boolean casNext(Node cmp, Node val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }
        
		//尝试CAS的将调用结点的item从cmp指向val
        final boolean casItem(Object cmp, Object val) {
            // assert cmp == null || cmp.getClass() != Node.class;
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        /**
         * Node的构造器
         */
        Node(Object item, boolean isData) {
            //使用unsafe的putObject方法,基于偏移量快速的赋值
            UNSAFE.putObject(this, itemOffset, item); // relaxed write
            //该值初始化之后不会被改变
            this.isData = isData;
        }

        /**
         * 将调用结点的next属性指向自己,即从队列中移除该结点
         * 仅仅在调用结点是原head结点并且更新head指向成功的之后才会调用,因此使用普通快速写入
         */
        final void forgetNext() {
            UNSAFE.putObject(this, nextOffset, this);
        }

        /**
         * 将调用结点的item设置为结点自己,将waiter置为null
         * 仅仅在某个结点被匹配或者被取消之后调用,因此使用普通快速写入
         */
        final void forgetContents() {
            UNSAFE.putObject(this, itemOffset, this);
            UNSAFE.putObject(this, waiterOffset, null);
        }

        /**
         * 如果调用结点被匹配或者被取消,那么返回true
         */
        final boolean isMatched() {
            //x保存此时调用结点的item值
            Object x = item;
            /*
             * 如果x等于自身,表示被取消或者匹配成功(执行了forgetContents方法);
             * 或者x不等于自身,获取x是否等于null的结果,如果该结果等于isData,表示被匹配了(还没执行forgetContents方法),因为:
             * 1、如果isData为true(入队、传递),并且结点没有被匹配和取消,那么x(item)一定不为null;
             * 2、如果isData为false(出队),并且结点没有被匹配和取消,那么x(item)一定为null
             * 以上两种情况,满足一种即返回true
             */
            return (x == this) || ((x == null) == isData);
        }

        /**
         * Returns true if this is an unmatched request node.
         */
        final boolean isUnmatchedRequest() {
            return !isData && item == null;
        }

        /**
         * 判断当前请求追加的结点是否不能追加到调用结点p后面
         */
        final boolean cannotPrecede(boolean haveData) {
            //获取调用结点的isData属性d
            boolean d = isData;
            Object x;
            /*
             * 如果d不等于haveData,表示当前请求追加的结点可以和调用结点p匹配
             * 并且调用结点的item值x不等于调用结点自己,表示调用结点p没有取消
             * 并获取x是否不等于null的结果,如果该结果等于d,表示调用结点p没有被匹配
             * 以上情况都满足,表明当前请求追加的结点可以和调用结点p匹配
             * 这个p结点一定是在第一步尝试查找并匹配结点之后新添加进来的,相当于错过了,
             * 即本来在队列中没找到,然后刚出了循环,到准备追加结点之间,又新来了结点,此时就不能追加,应该重试
             */
            return d != haveData && (x = item) != this && (x != null) == d;
        }

        /**
         * 尝试删除某个结点元素,简单的说就是尝试将item指向null,相当于被匹配了
         */
        final boolean tryMatchData() {
            //获取调用结点的item,使用x保存
            Object x = item;
            //如果x不为null,那么表示该结点没有被其他请求匹配(没有执行p.casItem(item, e))
            //并且如果x不等于调用结点自身,说明该结点也没有被取消(没有执行s.casItem(e, s)),也没有被完全匹配(没有执行s.forgetContents())
            //并且调用casItem尝试cas的将调用结点的item值从x改为null成功,返回true
            //以上三个条件都满足,都返回true,那么表示删除该元素成功
            if (x != null && x != this && casItem(x, null)) {
                //唤醒在该结点上阻塞等待的线程(如果有,就算没有也没关系)
            //如果存在线程被唤醒(只有传递数据的方法会有线程阻塞,传统入队列方法没有),那么这个线程将会安全的返回(相当于传递数据成功,不会抛出异常)
                LockSupport.unpark(waiter);
                return true;
            }
            return false;
        }

        private static final long serialVersionUID = -3375979862319811754L;

        //CAS操作都是使用UNSAFE来完成的,下面是UNSAFE相关初始化操作
        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);
            }
        }
    }
    
    /**
     * 在该队列的尾部插入指定的元素。 由于队列无限制,此方法将永远不会返回false
     */
    public boolean offer(E e) {
        //调用xfer方法,传递 e、true、ASYNC、0
        xfer(e, true, ASYNC, 0);
        return true;
    }
    
    /**
     * 所有的出队、入队、传递元素的方法的底层公用方法实现
     * @param e 对于入队、传递方法,e为指定元素,不能为null;对于出队方法,e为null
     * @param haveData 对于入队、传递方法,haveData为true;对于出队方法,haveData为false
     * @param how 四种方法模式:NOW, ASYNC, SYNC, TIMED
     * @param nanos    超时时间纳秒,仅在TIMED模式中使用
     */
    private E xfer(E e, boolean haveData, int how, long nanos) {
        //如果是入队、传递方法,但是e为null,那么抛出NullPointerException
        if (haveData && (e == null))
            throw new NullPointerException();
        //要追加到队列中的结点(可能会用到),可被复用
        Node s = null;                        // the node to append, if needed
		//开启一个死循环,完成不同方法自己的逻辑
        retry:
        for (;;) {                            // restart on append race
			//内部再开启第一层循环,用于尝试查找并匹配结点,这一步对于所有的方法都是一样代码
            //初始化条件:h保存最新的head,p保存h,从队头开始;判断条件:如果p不为null,那么继续循环
            for (Node h = head, p = h; p != null;) { // find & match first node
                //isData保存p的isData属性
                boolean isData = p.isData;
                //item保存p的item属性
                Object item = p.item;
                /*
                 * 1、如果item不等于p,表示p没有被取消,也没有被匹配(或者还没有匹配完毕)
                 * 2、获取item是否等于null的判断结果,如果结果等于isData,说明p还没有被匹配。
                 *    因为一般情况下item为null那么isData为false,item不为null那么isData为true
                 * 上面两个条件都满足,说明那么p没有被取消和匹配,当前请求就可能和p结点进行匹配
                 * 这类似于SynchronousQueue的公平模式的匹配机制
                 */
                if (item != p && (item != null) == isData) { // unmatched
                    //isData和当前请求的haveData属性一致,那就不能匹配,只有入队、传递操作和出队操作能够匹配
                    if (isData == haveData)   // can't match
                        /*
                         * break跳出第一层内循环,继续下面的步骤
                         * 这里可以直接跳出循环的原因就是新来的请求都是从队头到队尾的顺序点匹配的,如果前面有同一个类型的请求的结点没有被匹配
                         * 那么后来的同一个类型的请求肯定也不会匹配成功,该请求构造的结点会加入队列尾部。
                         */
                        break;
                    //调用casItem方法,尝试CAS的将p的item属性的值从item设置为e
                    //如果CAS成功,那么表示匹配成功
                    //如果CAS失败,那么表示匹配失败,可能是被取消了,或者被其他请求抢先匹配了
                    if (p.casItem(item, e)) { // match
                        /*
                         * 匹配成功之后,内部再开启第二层循环,尝试查找并移除被匹配或者取消的结点,以及更新head指向
                         * 初始化条件:q保存刚刚匹配的p;
                         * 判断条件:如果q不等于h,即如果p不是第一个循环的结点,那么继续循环
                         *
                         * 在第一层循环中,p最开始就是指向头结点的,如果第一次循环p就被匹配成功了,那么q = p也是指向头结点的
                         * 那么此时q等于h,那么就不会尝试移除被匹配的结点,而会直接返回,这里被匹配的结点将会留待后续请求再移除
                     */
                        for (Node q = p; q != h;) {
                            /*
                             * 第一次进入这里面,表示该请求一定是匹配成功了,并且匹配的结点也不是最开始的head结点—h,即h是已经被匹配了或者取消了的,此时一并移除
                             * 但是head是可能一直在变化的,此时的h也可能不再是真正的head结点了,即被其他线程改变了
                             */
                        	//获取q的后继n
                            Node n = q.next;  // update by 2 unless singleton
                            /*
                             * 如果此时h还是指向head头结点,即还没有被其他线程头结点,那么尝试cas的将head从h指向q(n为null)或者n(n不为null)
                             * 如果两边都为true,那么表示head指向更新成功,原h结点可以出队
                             */
                            if (head == h && casHead(h, n == null ? q : n)) {
                                //h的后继指向自己,h出队列
                            	//实际上最新head指向的结点的前驱结点在之后都会因为没有外部引用
                                //而被GC回收,只有原head结点出队的时候结点的next才会指向知己
                                h.forgetNext();
                                //跳出第二层内循环,继续下面的语句
                                break;
                            }                 // advance and retry
                            /*
                             * 将h指向此时的最新的head,如果:
                             * h为null
                             * 或者h不为null,q指向h的后继,如果q为null
                             * 或者h、q都不为null,如果q没有被匹配或者取消
                             */
                            if ((h = head)   == null ||
                                (q = h.next) == null || !q.isMatched())
                                //那么break跳出第二层内循环
                                break;        // unless slack < 2
                        }
                        //入队、传递、出队的方法的匹配逻辑都是一样的,匹配成功之后都会走到这一步
                    	//匹配成功之后,unpark唤醒p结点中的阻塞等待的线程
                        LockSupport.unpark(p.waiter);
                        //匹配成功之后,返回p原来的item的值,xfer方法结束,
                        //如果该请求是入队、传递方法,那么应该放那会null,如果是出队方法,则返回出队的元素
                        return LinkedTransferQueue.<E>cast(item);
                    }
                }
                //获取p的后继n,向后继续匹配
                Node n = p.next;
                /*
                 * 判断p是否不等于n,即p是否没有出队,如果是,那么p设置为n,继续下一轮第一层循环,
                 * 相当于继续匹配后继,如果不是,那么h设置为此时的head,p设置为h,继续下一轮第一层
                 * 循环,相当于重新开始第一层循环,这里说明,xfer方法同时允许多个请求匹配多个结点,
                 * 但是如果遇到某个结点出队列了,那么重新获取head,重新开始
                 */
                p = (p != n) ? n : (h = head); // Use head if p offlist
            }
    		//如果how不等于NOW,即表示:
            //如果是ASYNC、SYNC、TIMED的模式,那么进入if代码块
            //如果是NOW的模式,即不会阻塞的其他方法,出队 - poll()、remove()、尝试传递 - tryTransfer(e),这两个方法就算匹配失败也直接返回,不会进入if代码块
            if (how != NOW) {                 // No matches available
                //要追加到队列中的结点s如果为null,那么初始化
                if (s == null)
                    //那么新建Node结点,item属性为e,isData属性为haveData,即item为入队、传递的元素
                    s = new Node(e, haveData);
                //调用tryAppend尝试追加s结点到队列尾部,返回值为pred,追加成功则返回s的前驱,没有前驱就返回s自己;追加失败就返回null
                Node pred = tryAppend(s, haveData);
                //如果pred为null,表示追加失败,那么结束本次最外层的大循环,相当于重头再来走一次,包括匹配的步骤
                if (pred == null)
                    continue retry;           // lost race vs opposite mode
                //1 如果是SYNC、TIMED的模式,表示等待和超时等待的方法,那么进入if代码块
                //2 如果是ASYNC的模式,即不会阻塞的入队方法,入队 - offer(e)、put(e)、offer(e, timeout, unit)、add(e)
                if (how != ASYNC)
                    /*
                     * 剩下的就是等待的方法了,调用awaitMatch方法阻塞等待直到被后续的请求匹配或者被取消
                     * 这是需要等待的一系列方法在主动匹配失败之后进行等待被动匹配并返回的统一出口
                     * 这里通过判断how是否等于TIMED模式来判断是否是超时等待模式
                     * 如果被匹配成功,返回匹配的请求的item值;如果因为中断或者超时被取消等待,返回当前结点的e(item)
                     */
                    return awaitMatch(s, pred, e, (how == TIMED), nanos);
            }
            return e; // not waiting
        }
    }
    
    /**
     * 获取并移除此队列的头部,在队列没有入队、传递元素的结点或者没有入队、传递元素的请求之前一直等待
     * @return 被移除的队列头部元素值
     * @throws InterruptedException 如果等待被中断
     */
    public E take() throws InterruptedException {
        //调用xfer方法,传递null、false、SYNC、0
        E e = xfer(null, false, SYNC, 0);
        //如果e不等于null,表示获取到了获取到了元素,那么返回e
        if (e != null)
            return e;
        //否则,只能是因为等待被中断而返回的情况,抛出InterruptedException
        Thread.interrupted();
        throw new InterruptedException();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值