jdk中的queue

  • 1- 需求背景
  • 2- LinkedBlockingQueue源码分析
    • 2-1 重要成员变量
    • 2-2 插入方法
      • 2-2-1 put方法
      • 2-2-2 offer方法
      • 2-2-3 boolean offerE e long timeout TimeUnit unit方法
    • 2-3 读取方法
      • 2-3-1 take方法
      • 2-3-2 poll方法
      • 2-3-3 E polllong timeout TimeUnit unit方法
    • 2-4 dequeue和enqueue方法
      • 2-4-1 dequeue方法
      • 2-4-2 enqueue方法

1- 需求背景

最近项目中有这样一个需求,因为在恢复WAL的过程中,以前是把WAL文件中的记录顺序的读出来,插入到阻塞队列(LinkedBlockingQueue)中,供消费端消费。后来发现顺序读取多个WAL文件插入队列的方式太慢,考虑使用并行WAL文件读取的方式,但是在插入队列时需要保证记录出队列时保持有序,于是考虑把LinkedBlockingQueue改成PriorityQueue,使用记录的id作为优先级完成有序出队列,但是发现PriorityQueue不是线程安全的,于是找到了PriorityBlockingQueue,说是阻塞的,但是发现提供的几个入队方法都是非阻塞的。那么问题来了,我们需要一个:

  • 阻塞的
  • 线程安全的
  • 顺序出队列的
    于是考虑封装PriorityBlockingQueue

2- LinkedBlockingQueue源码分析

2-1 重要成员变量

用来存储队列中的元素,是个模板类

    /**
     * Linked list node class
     */
    static class Node<E> {
        E item;
        Node next;
        Node(E x) { item = x; }
    }

其他成员变量,就不一一解释了,代码中给出了明确的解释,下边分析方法时会进一步解释

    private final int capacity;//总大小,队列元素个数不能超过这个总大小

    /** Current number of elements */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * Head of linked list.
     * Invariant: head.item == null
     */
    transient Node head;

    /**
     * Tail of linked list.
     * Invariant: last.next == null
     */
    private transient Node last;

    /** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();

2-2 插入方法

put(E e): 阻塞插入方法
offer(E e, long timeout, TimeUnit unit):非阻塞插入方法,但是会等待一个超时时间,超时才返回false
boolean offer(E e):非阻塞插入方法,不等待超时,如果不能插入就返回false

2-2-1 put方法

原型:public void put(E e) throws InterruptedException
这是一个阻塞方法,如果队列满了,就等待

        if (e == null) throw new NullPointerException();//元素为空则抛异常
        int c = -1;
        Node node = new Node(e);
        final ReentrantLock putLock = this.putLock;//获取插入锁变量的引用
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();//阻塞其他插入线程
        try {
            while (count.get() == capacity) {//如果当前元素个数等于容量上限,则等待在notFull这个条件变量上,
                                             //等待其他插入线程或者读取线程发现元素个数小于总容量时唤醒这个等待的插入线程
                notFull.await();
            }
            enqueue(node); //把元素入队列,enqueue方法下边分析其实现
            c = count.getAndIncrement();//元素个数加1
            if (c + 1 < capacity)//加1后的元素个数如果小于容量,则通知等待在notFull条件变量的插入线程醒来
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();//主要是调用notEmpty.signal(),那么哪些线程等待在notEmpty上呢?
                             //肯定是读取线程,一个在take方法中,一个在poll(long timeout, TimeUnit unit)
                             //方法中,poll方法中却没有,为什么?因为第一个poll方法,如果队列中没有元素,
                             //在超时时间内是可以接受的,所以这个poll会等待一会,在等待的时间内可以被唤醒。
                             //而第二个poll方法,如果队列元素总数为0,即empty状态,不会等待,而是直接返回
                             //null

2-2-2 offer方法

方法原型:public boolean offer(E e)
非阻塞方法,如果元素总数小于容量,则插入,并返回true,如果大于或者等于容量,则返回false

        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;
        int c = -1;
        Node node = new Node(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            if (count.get() < capacity) {//元素总数小于容量,则入队列
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)//还没满的话,则通知等待在notFull上的线程。
                                     //问题,为什么在count递增之后还有再加一个1来进行判断呢???
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();//在put方法中已经解释过
        return c >= 0;

2-2-3 boolean offer(E e, long timeout, TimeUnit unit)方法

        if (e == null) throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);//等待超时,如果超时,nanos会是个负数,再次进入while循环时,直接返回false
            }
            enqueue(new Node(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)//同样不知道什么递增之后还要加1判断
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return true;

2-3 读取方法

2-3-1 take()方法

如果队列没有元素,则阻塞等待

        E x;
        int c = -1;
        final AtomicInteger count = this.count;//如果在这里获取到队列大小后,但是没有获取到takelock,其他线程更新了count,难道不会导致当前元素个数不一致???
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {//如果没有元素,就等待被唤醒
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();//notFull.signal(),很多put和offer(timeout)方法都因为队列满了而等待被唤醒,而这里因为有一个元素出队列,所以容量不满,可以通知那些在等待的线程
        return x;

2-3-2 poll()方法

如果没有元素,则返回null

        final AtomicInteger count = this.count;
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;

2-3-3 E poll(long timeout, TimeUnit unit)方法

如果没有元素,不直接返回null,而是等待一个超时时间

        E x = null;
        int c = -1;
        long nanos = unit.toNanos(timeout);
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;

2-4 dequeue和enqueue方法

2-4-1 dequeue方法

        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;

2-4-2 enqueue方法

last = last.next = node;

你可能感兴趣的:(tools)