Java-并发编程-02并发包-04阻塞队列


1 阻塞队列

  • 当队列为空时,读取等待,当队列满时,写入等待
  • 常用方法
抛出异常返回值阻塞超时
入队addofferputoffer(e, time,unit)
出队removepolltakepoll(time,unit)
检查elementpeek//

2 ArrayBlockingQueue

  • 是线程安全的有界阻塞队列,底层是基于数组实现的,物理上是一个数组,逻辑层面上是一个环形结构
  • 构造时需要制定容量,并且可以选择是否公平性,如果公平设置为true,等待时间最长的线程会有限得到处理
  • 使用全局独占锁实现了同时只有一个线程进行出队/入队操作
  • offer/poll通过简单的加锁实现了入队/出队操作
  • put/take使用条件变量Condition实现了队列满/空则等待,分别在出队/入队操作中发送信号激活等待线程实现同步
  • size()方法返回值是精确的,因为直接返回count,count的操作都是全局加锁的

2.1 结构

public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
	final Object[] items;	//存放队列元素
	int takeIndex;			//出队下标
	int putIndex;			//入队下标
	int count;				//统计元素个数
	final ReentrantLock lock;			// 独立锁,保证出入队列原子性
	private final Condition notEmpty;	//条件变量,控制出队原子性
	private final Condition notFull;	//条件变量,控制入队原子性
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
}

2.2 add(E e)

非阻塞方法,加入元素到队尾,如果队列满直接抛出异常

	// 直接调用父类AbstractQueue#add方法
    public boolean add(E e) {
        return super.add(e);
    }
    // AbstractQueue#add调用offer(E e),队列满直接抛异常
    public boolean add(E e) {
        if (offer(e)) return true;
        else throw new IllegalStateException("Queue full");
    }

2.3 offer(E e)

非阻塞方法,e为null直接抛异常,在队列尾部插入一个元素,如果队列未满则插入成功后返回true,如果队列已满则丢弃当前元素然后返回false

    public boolean offer(E e) {
    	// 确保e!=null
        checkNotNull(e);
        // 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();	// 不响应中断标志
        try {
        	// 队列满直接返回false
            if (count == items.length) return false;
            else {
            	// 插入元素
                enqueue(e);
                return true;
            }
        } finally {
        	// 释放锁,会把修改的共享变量值count刷新到主内存中
            lock.unlock();
        }
    }
    private void enqueue(E x) {
        // 此时肯定持有锁,且items[putIndex]==null
        final Object[] items = this.items;
        // 放入最新元素至items数组
        items[putIndex] = x;
        // 计算下一个元素存放下标,新入队下标=items总长度时下标置为0(循环队列)
        if (++putIndex == items.length) putIndex = 0;
        // 递增元素计数器,count是共享变量,但是之前获取了锁,所以不存在内存不可见问题,加锁后都是从主内存获取变量,而不是从CPU缓存或寄存器获取
        count++;
        // 唤醒一个在notEmpty队列因为take而阻塞的线程
        notEmpty.signal();  
    }

2.4 put(E e)

是阻塞方法,e为null直接抛异常,向队列尾部插入一个元素,如果队列未满则插入成功后直接返回true,如果队列已满则直接阻塞当前线程直到队列有空闲并插入成功后返回true,如果阻塞时被其他线程设置了中断标志,则被阻塞线程会抛出InterruptedException异常而返回

    public void put(E e) throws InterruptedException {
    	// 确保e!=null
        checkNotNull(e);
        // 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();	//可响应中断的锁
        try {
        	// 如果队列满则将当前线程放入notFull的等待队列
            while (count == items.length)
                notFull.await();
            // 队列不满则插入元素
            enqueue(e);
        } finally {
        	// 释放锁,会把修改的共享变量值count刷新到主内存中
            lock.unlock();
        }
    }

2.5 remove()

非阻塞方法,队列中移除指定的元素,队列中存在该元素,移除成功后返回true,不存在返回false

    public boolean remove(Object o) {
    	// o==null 直接返回false
        if (o == null) return false;
        final Object[] items = this.items;
        // 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 如果队列不为空
            if (count > 0) {
                final int putIndex = this.putIndex;
                int i = takeIndex;
                do {
                	// 如果从items中找到o,则直接删除items中对应元素,并返回true
                    if (o.equals(items[i])) {
                        removeAt(i);
                        return true;
                    }
                    // 扫描到队尾后将i置为0(循环数组)
                    if (++i == items.length) i = 0;
                } while (i != putIndex);//确保完整的找一遍
            }
            return false;
        } finally {
        	// 释放锁,会把修改的共享变量值count刷新到主内存中
            lock.unlock();
        }
    }
    void removeAt(final int removeIndex) {
        // 当前一定持有锁,且removeIndex有值,且remove有效
        final Object[] items = this.items;
        // 如果要删除的元素为头部元素
        if (removeIndex == takeIndex) {
			// 头部元素置为null
            items[takeIndex] = null;
            // 计算下一个头部元素,当头部元素到达数组尾部则置为0(循环数组)
            if (++takeIndex == items.length) takeIndex = 0;
            // 递减元素计数器
            count--;
            // 删除头部元素时调用迭代器的elementDequeued()方法
            if (itrs != null) itrs.elementDequeued();
        } else {
            // 从非头部元素删除
            final int putIndex = this.putIndex;
            for (int i = removeIndex;;) {
            	// 下一个元素索引
                int next = i + 1;
                // 到达items尾部则置为0(循环数组)
                if (next == items.length) next = 0;
				// 如果未完成遍历
                if (next != putIndex) {
                	// 删除元素下标后的元素都向前移一位
                    items[i] = items[next];
                    i = next;
                } else {
                	// 所有元素都完成向前移动一位后,此时item[i-1]==item[i]
                	// 需要将多余的items[i]置为null,且将putIndex置为i
                    items[i] = null;
                    this.putIndex = i;
                    break;
                }
            }
            // 递减元素计数器
            count--;
            // 删除非头部元素,要调用迭代器的removeAt(index)方法
            if (itrs != null) itrs.removedAt(removeIndex);
        }
        // 唤醒一个在notFull队列上使用put而阻塞的线程
        notFull.signal();
    }

2.6 poll()

非阻塞方法,获取头部元素并移除,队列为空直接返回null

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
    private E dequeue() {
        // 一定持有锁,且items[takeIndex]!=null
        final Object[] items = this.items;
        // 获得头部元素对象
        E x = (E) items[takeIndex];
        // 将原有头部元素置为null
        items[takeIndex] = null;
        // 计算下一个takeIndex
        if (++takeIndex == items.length) takeIndex = 0;
        // 递减元素计数器
        count--;
        // 删除头部元素时调用迭代器的elementDequeued()方法
        if (itrs != null) itrs.elementDequeued();
        // 唤醒一个在notFull队列上使用put而阻塞的线程
        notFull.signal();
        return x;
    }

2.7 take()

是阻塞方法,获取队头元素的值,并从队列中移除该元素,如果队列为空则线程进入阻塞,阻塞时被中断会抛出异常

    public E take() throws InterruptedException {
    	//获取可响应中断的独立锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
        	// 队列为空则线程进入notEmpty的等待队列, 由于线程被唤醒后也需要判断items数组中是否有元素,所以使用while判断
            while (count == 0) notEmpty.await();
            // 获取头部元素
            return dequeue();
        } finally {
        	// 释放锁
            lock.unlock();
        }
    }

2.8 peek()

获取队头下标的值并返回

    public E peek() {
    	// 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 获取当前takeIndex下标的元素,可能为null
            return itemAt(takeIndex); 
        } finally {
        	// 释放锁
            lock.unlock();
        }
    }
    // 直接从items中获取下标i的元素
    final E itemAt(int i) {
        return (E) items[i];
    }

2.9 size()

返回当前队列元素计数器

    public int size() {
    	// 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 由于count未被声明为volatile,所以为了保证可见性,需要加锁
            return count;
        } finally {
            lock.unlock();
        }
    }

2.10 使用

    public void testPriorityBlockingQueue(){
        // BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
        BlockingQueue<Integer> queue = new PriorityBlockingQueue<>(200, (o1, o2) -> o2-o1);
        for (int i = 0; i < 200; i++) {
            queue.add(new Random().nextInt(500));
        }
        System.out.println(queue);
        while(!queue.isEmpty()){
            System.out.println(queue.poll());
        }
    }

3 LinkedBlockingQueue

  • LinkedBlockingQueue是线程安全的无界非阻塞队列,底层是单向链表实现的,出队和入队操作都是使用CAS实现线程安全
  • 默认情况下LinkedBlockingQueue的容量是Integer.MAX_VALUE,也可以在初始化时指定其最大容量
  • LinkedBlockingQueue内部有两个Node分别保存首尾节点,有个AtomicInteger的原子变量count来记录元素个数
  • 入队操作尾节点,出队操作头节点,使用不同的ReentrantLock锁控制出队和入队的原子性

3.1 结构

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
    static class Node<E> {
        E item;
        Node<E> next;
        Node(E x) { item = x; }
    }
	// 初始化容量,没有设置则为Integer.MAX_VALUE
    private final int capacity;
	// 当前元素的数量
    private final AtomicInteger count = new AtomicInteger();
	// 链表头节点
    transient Node<E> head;
	// 链表尾节点
    private transient Node<E> last;
	// take重入锁
    private final ReentrantLock takeLock = new ReentrantLock();
	// take方法阻塞条件队列
    private final Condition notEmpty = takeLock.newCondition();
	// put重入锁
    private final ReentrantLock putLock = new ReentrantLock();
	// put方法条件阻塞队列
    private final Condition notFull = putLock.newCondition();
}

3.2 offer(E e)

为非阻塞方法向队列尾部插入一个元素,如果队列中有空闲则插入成功并返回true,如队列已满则丢弃当前元素并返回false,如果e为null,直接抛出NPE

// offer方法通过使用putLock锁保证了在队尾新增元素的原子性
public boolean offer(E e) {
	// NPE判断
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    // 当前队列满则丢弃将要放入的元素,并且返回false
    if (count.get() == capacity) return false;
    // 成功标识,如果>=0位成功,负数为失败
    int c = -1;
    // 构造新节点,获取putLock锁
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
    	// 如果队列不满则进入队列,并递增元素个数
        if (count.get() < capacity) {
            enqueue(node);
            // 更新操作标识
            c = count.getAndIncrement();
            // 如果队列未满,则唤醒一个在notFull上调用put而阻塞的线程
            if (c + 1 < capacity) notFull.signal();
        }
    } finally {
    	// 释放putLock
        putLock.unlock();
    }
    // c==0代表put成功,当前队列容量不为空,则唤醒一个在notEmpty上调用take而阻塞的线程
    if (c == 0) signalNotEmpty();
    return c >= 0;
}
private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    last = last.next = node;
}
private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

3.3 put(E e)

为阻塞方法, 向队尾部插入一个元素,如果队列中有空闲则插入后直接返回,如果队列已满则阻塞当前队列,直到队列有空闲且插入成功再返回

  • 如果在阻塞时其他线程设置了中断标志,则被阻塞线程会抛出InterruptedException异常返回
    public void put(E e) throws InterruptedException {
    	// NPE判断
        if (e == null) throw new NullPointerException();
	    // 成功标识,如果>=0位成功,负数为失败
        int c = -1;
        // 构造新节点,获取putLock锁
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            // 如果队列满则线程进入notFull的等待队列
            // 选while不选if为了防止假唤醒
            while (count.get() == capacity) {
                notFull.await();
            }
            // 入队操作
            enqueue(node);
            // 更新标识
            c = count.getAndIncrement();
            // 如果队列未满,则唤醒一个在notFull上调用put而阻塞的线程
            if (c + 1 < capacity) notFull.signal();
        } finally {
            putLock.unlock();
        }
        // c==0代表put成功,当前队列容量不为空,则唤醒一个在notEmpty上调用take而阻塞的线程
        if (c == 0) signalNotEmpty();
    }

3.4 poll()

从队列头部获取并移除一个元素,如果队列为空则返回null,是不阻塞方法

这里关于count的操作,仅在出队时加锁控制 ,线程安全设计

  1. 所有获取操作中都有takeLock锁,所以poll,take,remove方法不会走到修改count技术
  2. 在所有设置操作中都会获取putLock锁,包括put,offer操作,都是增加count,不会影响逻辑
public E poll() {
    final AtomicInteger count = this.count;
    if (count.get() == 0) return null;
    E x = null;
    // 初始化操作标识
    int c = -1;
    // 获取takeLock
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
    	// 队列不为空,则dequeue出队,并递减count
        if (count.get() > 0) {
            x = dequeue();
            c = count.getAndDecrement();
            // 移除元素后队列不为空,则唤醒在notEmpty上因调用take方法而被阻塞的线程
            if (c > 1) notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    // 移除队头元素后当前队列还是满的,移除对头元素后当前队列至少有一个空闲位置,可以通知在notFull上因调用put而阻塞的线程
    if (c == capacity) signalNotFull();
    return x;
}
private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    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;
}
private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}

3.5 peek()

非阻塞方法,获取队列头部元素但是不从队列里面移除,如果队列为空返回null

public E peek() {
	// count.get()==0不是原子操作,所以需要再加入takeLock锁
    if (count.get() == 0) return null;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        Node<E> first = head.next;
        if (first == null) return null;
        else return first.item;
    } finally {
        takeLock.unlock();
    }
}

3.6 take()

获取当前队列头部元素并从队列里移除它,队列为空则阻塞当前线程直到队列不为空然后返回元素,如果在阻塞时被其他线程设置了中断标志,则被阻塞线程会抛出InterruptedException异常返回

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
    	// while防止假唤醒, 队列为空则线程进入notEmpty的等待队列
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        // 移除元素后队列不为空,则唤醒在notEmpty上因调用take方法而被阻塞的线程
        if (c > 1) notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    // 移除队头元素后当前队列还是满的,移除对头元素后当前队列至少有一个空闲位置,可以通知在notFull上因调用put而阻塞的线程
    if (c == capacity) signalNotFull();
    return x;
}

3.7 remove(Object o)

删除队列里面指定的元素,返回删除操作结果

public boolean remove(Object o) {
    if (o == null) return false;
    fullyLock();
    try {
        for (Node<E> trail = head, p = trail.next;
             p != null;
             trail = p, p = p.next) {
            if (o.equals(p.item)) {
                unlink(p, trail);
                return true;
            }
        }
        return false;
    } finally {
        fullyUnlock();
    }
}
void fullyUnlock() {
    takeLock.unlock();
    putLock.unlock();
}
void unlink(Node<E> p, Node<E> trail) {
    // assert isFullyLocked();
    // p.next is not changed, to allow iterators that are
    // traversing p to maintain their weak-consistency guarantee.
    p.item = null;
    trail.next = p.next;
    if (last == p) last = trail;
    if (count.getAndDecrement() == capacity)
        notFull.signal();
}

3.8 size()

获取当前队列元素个数

public int size() {
    return count.get();
}

4 PriorityBlockingQueue

  • PriorityBlockingQueue是带优先级的无界阻塞队列,每次出队都返回优先级最高或者最低的元素
  • 使用数组作为元素存储的数据结构,数组是可通过CAS扩容的
  • 是对PriorityQueue的再次包装,是基于堆数据结构的, 内部是使用平衡二叉树堆实现的,导致直接遍历元素不保证有序性
  • PriorityBlockingQueue出队始终保证出队元素是堆树的根节点
  • 默认使用对象的compareTo方法提供比较规则

4.1 结构

public class PriorityBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
	// 默认初始化容量
	private static final int DEFAULT_INITIAL_CAPACITY = 11;
	
	// 最大容量,为了保证兼容性,一些虚拟机会在数组对象头部设置一些信息会占用空间,保证不会OutOfMemoryError
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
    // 队列元素,采用平衡二叉树堆实现,queue[n]的两个children为queue[2*n+1],queue[2*(n+1)], comparator为空则按照下标排序
    private transient Object[] queue;
    
    // 元素个数
    private transient int size;

    // 比较器,默认为null
    private transient Comparator<? super E> comparator;

    // 重入锁
    private final ReentrantLock lock;
    
    // take方法阻塞条件队列, 由于是无界队列,所以put方法时非阻塞的
    private final Condition notEmpty;

    // 自旋锁,状态为0/1, 0表示没有在扩容,1表示正在扩容
    private transient volatile int allocationSpinLock;

    private PriorityQueue<E> q;

    public PriorityBlockingQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    public PriorityBlockingQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
    public PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator) {
        if (initialCapacity < 1) throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }

}

4.2 offer(E e)

在队列中插入一个元素,由于队列是无界的,所以一直返回true

    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        // 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        int n, cap;
        Object[] array;
        // n为当前元素个数size,array 为当前队列元素,cap为队列容量
        // 自旋检查当前是否扩容完毕,扩容完毕退出while循环
        // 如果当前元素个数>=队列容量,则以CAS方式进行扩容
        while ((n = size) >= (cap = (array = queue).length))
            tryGrow(array, cap); //队列扩容操作
        try {
            Comparator<? super E> cmp = comparator;
            // 默认比较器
            if (cmp == null) siftUpComparable(n, e, array);
			// 自定义比较器
            else siftUpUsingComparator(n, e, array, cmp);
            // 队列元素+1
            size = n + 1;
            // 唤醒一个在notEmpty等待队列中由于执行take而阻塞的线程
            notEmpty.signal();
        } finally {
        	// 释放锁
            lock.unlock();
        }
        return true;
    }
    
	// 扩容操作
    private void tryGrow(Object[] array, int oldCap) {
		// 释放获取的锁,扩容时采用CAS方式,释放锁后出队操作不会影响, 提高信念
        lock.unlock(); 
        Object[] newArray = null;
        // 判断当前没有在扩容,且用cas方式将扩容自旋标志改为1 正在扩容
        if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,0, 1)) {
            try {
            	// old<64则执行oldcap+2,oldCap>64则扩容50%
                int newCap = oldCap + ((oldCap < 64) ?(oldCap + 2) :(oldCap >> 1));
                // overflow 判断
                if (newCap - MAX_ARRAY_SIZE > 0) {
                	// newCap overflow了,则最小化扩容
                    int minCap = oldCap + 1;
                    // minCap有效性判断,看是否可以继续扩容
                    if (minCap < 0 || minCap > MAX_ARRAY_SIZE) throw new OutOfMemoryError();
                    // 置为最大容量
                    newCap = MAX_ARRAY_SIZE;
                }
                // 二级校验扩容条件判断,新容量>旧容量,且队列未被修改则进行扩容
                if (newCap > oldCap && queue == array)
                    newArray = new Object[newCap];
            } finally {
            	// 扩容标识改为0
                allocationSpinLock = 0;
            }
        }
        // T1线程CAS成功后newArray!=null,T2线程CAS失败,newArray==null,此时让T2线程让出cpu,让T1线程尽快重新获取锁
        // 若T2.yield()后又重新获得了锁,则不会执行扩容,外层while判断扩容失败会继续调用CAS扩容
        if (newArray == null) Thread.yield();
        // 重新获取锁
        lock.lock();
        // 新容器!=null 并且 原容器未被修改则进行数据复制,完成扩容
        if (newArray != null && queue == array) {
        	// 指向新容器
            queue = newArray;
            // 完成容器内数据复制
            System.arraycopy(array, 0, newArray, 0, oldCap);
        }
    }
    
	// 默认比较器 维护二叉平衡树
	// k 当前容器中元素个数,x 要插入的元素,array 容器
    private static <T> void siftUpComparable(int k, T x, Object[] array) {
        Comparable<? super T> key = (Comparable<? super T>) x;
        // 容器中元素个数k>0则判断插入位置, k==0则直接插入
        while (k > 0) {
        	// 计算parent
            int parent = (k - 1) >>> 1;
            // 取得parent的值
            Object e = array[parent];
            // 判断待插入元素与k下标其对应parent的大小
            if (key.compareTo((T) e) >= 0) break;
            // 原来parent上浮
            array[k] = e;
            // 更新需要判断下标为parent下标
            k = parent;
        }
        array[k] = key;
    }
	
	// 自定义比较器 维护二叉平衡树
    private static <T> void siftUpUsingComparator(int k, T x, Object[] array,Comparator<? super T> cmp) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = array[parent];
            if (cmp.compare(x, (T) e) >= 0) break;
            array[k] = e;
            k = parent;
        }
        array[k] = x;
    }

4.3 poll()

// 获取队列内二叉堆的根节点元素,队列为空直接返回null

    public E poll() {
    	// 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return dequeue();
        } finally {
	        // 释放独占锁
            lock.unlock();
        }
    }
    private E dequeue() {
    	// 成功出队后n=size-1
        int n = size - 1;
        if (n < 0) return null;
        else {
        	// 获取容器队列引用
            Object[] array = queue;
            // 获取队头元素
            E result = (E) array[0];
            // 获取队尾元素
            E x = (E) array[n];
            // 队尾置null
            array[n] = null;
            Comparator<? super E> cmp = comparator;
            // 原有队尾元素取出,进行下沉
            if (cmp == null) siftDownComparable(0, x, array, n);
            else siftDownUsingComparator(0, x, array, n, cmp);
            size = n;
            return result;
        }
    }
	// 二叉堆下沉操作
    private static <T> void siftDownComparable(int k, T x, Object[] array,int n) {
        if (n > 0) {
            Comparable<? super T> key = (Comparable<? super T>)x;
            int half = n >>> 1;           // loop while a non-leaf
            while (k < half) {
                int child = (k << 1) + 1; // assume left child is least
                Object c = array[child];
                int right = child + 1;
                if (right < n &&
                    ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                    c = array[child = right];
                if (key.compareTo((T) c) <= 0)
                    break;
                array[k] = c;
                k = child;
            }
            array[k] = key;
        }
    }

    private static <T> void siftDownUsingComparator(int k, T x, Object[] array,int n,Comparator<? super T> cmp) {
        if (n > 0) {
            int half = n >>> 1;
            while (k < half) {
                int child = (k << 1) + 1;
                Object c = array[child];
                int right = child + 1;
                if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
                    c = array[child = right];
                if (cmp.compare(x, (T) c) <= 0)
                    break;
                array[k] = c;
                k = child;
            }
            array[k] = x;
        }
    }

4.4 put(E e)

内部调用offer操作,无界队列所以无阻塞

    public void put(E e) {
        offer(e); // never need to block
    }

4.5 take()

获取队列内部堆树的根节点元素,队列为空则阻塞

    public E take() throws InterruptedException {
        // 获取可响应中断的锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        E result;
        try {
        	// 进行出队操作(返回堆树的根节点元素),队列为null进入notEmpty阻塞队列
        	// 使用while循环而不是if是为了避免虚假唤醒
            while ((result = dequeue()) == null) notEmpty.await();
        } finally {
        	// 释放锁
            lock.unlock();
        }
        return result;
    }

4.6 size()

计算队列元素个数

    public int size() {
    	// 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// size没有加volatile,所以需要加锁,保证size的可见性,防止此时其他线程进行出队和入队操作
            return size;
        } finally {
            lock.unlock();
        }
    }

5 DelayQueue

  • 一个无界阻塞延迟队列,基于PriorityQueue实现,只有在延迟期满时才能从中提取元素
  • DelayQueue是一个没有大小限制的队列,所以生产者永远不会被阻塞,只有消费者被阻塞
  • 队列的头部是延迟期满后保存时间最长的Delayed元素,如果延迟都没满期,则队列没有头部,poll返回null
  • 当一个元素的getDelay(TimeUnit.NANOSECOUNDS)方法返回一个小于或等于0的值时,则出现期满,poll就可以移除这个元素
  • 使用场景
    • 关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之
    • 缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出
    • 任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求

5.1 结构

// DelayQueue队列中的元素需要实现Delayed接口,每个元素都需有过期时间
public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
	// 重入锁实现线程同步
    private final transient ReentrantLock lock = new ReentrantLock();
    // 使用优先级队列存放元素
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    // 基于Leader-Follower模式,为了减少不必要的线程等待设计
    // 线程调用take方法会变成leader,会调用条件变量available.awaitNanos(delay)进行有限时间的等待,其他线程为follower线程,会调用available.await()进行无限时间等待. leader线程延迟时间过期后,会退出take方法,并通过available.signal()方法唤醒一个follower线程,被唤醒的follower线程被选举为新的leader线程
    private Thread leader = null;

    // 根据leader/follower线程执行不同的等待
    private final Condition available = lock.newCondition();

    public DelayQueue() {}
    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }
}

5.2 offer(E e)

插入元素到队列中,如果插入元素为null则抛异常,由于是无界队列,所以总是返回true,插入元素需要实现Delayed接口

    public boolean offer(E e) {
    	// 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 插入到优先级队列中
            q.offer(e);
            // 查看将要过期的元素,如果为新插入元素,则代表当前元素最先过期
            if (q.peek() == e) {
            	// leader 释放
                leader = null;
                // 唤醒一个在available队列因为调用take方法阻塞的线程
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

5.3 take()

获取并移除队列里面已过期的元素,如果队列中没有已过期元素则等待

    public E take() throws InterruptedException {
    	// 获取可响应中断的锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
            	// 查看头部元素
                E first = q.peek();
                // 队列为空,将当前线程放入available的等待队列,offer操作会唤醒此处等待的一个线程,此时会重新循环,查看头部元素进行比较
                if (first == null) available.await();
                else {
                	// 查看头部元素TTL
                    long delay = first.getDelay(NANOSECONDS);
					// 已过期则从优先级队列中取出元素
                    if (delay <= 0) return q.poll();
                    // 未过期则释放头部元素的引用
                    first = null; 
                    // 已有leader表面其他线程正在执行take,本线程则无限等待,leader普通变量,因为加锁,所以安全
                    if (leader != null) available.await();
                    else {
                    	// 没有leader,则设置自己为leader
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        	// 等待delay时间,并在外层finally释放锁
                            available.awaitNanos(delay);
                        } finally {
                        	// 判断是否leader线程
                            if (leader == thisThread)
                            	// leader线程awaitNanos(delay)时间后重新竞争得到锁后,重置leader为null,重新进入循环,此时队头元素已过期
                                leader = null;
                        }
                    }
                }
            }
        } finally {
	        // 如果优先级队列存在元素
            if (leader == null && q.peek() != null)
            	// 唤醒一个在available队列执行take方法而阻塞的线程
                available.signal();
            // 释放锁
            lock.unlock();
        }
    }

5.4 poll()

获取并移除队头过期元素,如果没有过期元素则返回null

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek();
            // 队列为空或队头元素没有过期则返回null
            if (first == null || first.getDelay(NANOSECONDS) > 0)
                return null;
            else
                return q.poll();
        } finally {
            lock.unlock();
        }
    }

5.5 size()

计算队列元素个数,包含过期和没过期的

    public int size() {
    	// 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 返回优先级队列大小
            return q.size();
        } finally {
        	// 释放锁
            lock.unlock();
        }
    }

5.6 使用

  • 创建延迟对象通过继承Delayed接口
  • 重写getDelay,compareTo 方法
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<DelayTask> delayQueue = new DelayQueue<>();
        Random random = new Random();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            delayQueue.offer(new DelayTask(random.nextInt(5 * 1000), startTime));
        }
        DelayTask task;
        for (; ; ) {
            while ((task = delayQueue.take()) != null) {
                task.print();
            }
        }
    }
    class DelayTask implements Delayed {

    private final long timeStamp;
    private final long startTime;
    private final long delayTime;

    public DelayTask(long delayTime, long startTime) {
        this.delayTime = delayTime;
        this.timeStamp = System.currentTimeMillis();
        this.startTime = startTime;
    }

    public long getExpectTime() {
        return timeStamp + delayTime;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert((this.timeStamp + this.delayTime) - System.currentTimeMillis(), TimeUnit.NANOSECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        if (this.getExpectTime() > ((DelayTask) o).getExpectTime()) {
            return 1;
        } else if (this.getExpectTime() < ((DelayTask) o).getExpectTime()) {
            return -1;
        }
        return 0;
    }

    public void print() {
        long now = System.currentTimeMillis();
        long realDelayTime = now - this.timeStamp;
        long Deviation = realDelayTime - this.delayTime;

        System.out.println(Thread.currentThread().getName() + "--延迟时间是:"
                + this.delayTime + "..真实延迟时间.......:" + realDelayTime + "......误差时间(单位毫秒)..::" +
                Deviation + ",此时完成任务时间共经历时间: " + (now - startTime));
    }
}

6 SynchrohousQueue

  • 一种无缓冲的等待队列
  • 采用公平模式:配合FIFO队列来阻塞多余的生产者和消费者,从而体系整体公平的策略
  • 采用非公平模式:配合LIFO队列来管理多余的生产者和消费者
  • 同步队列,但是队列长度为0,生产者放入队列的操作会被阻塞,直到消费者过来取,所以这个队列根本不需要空间存放元素;有点像一个独木桥,一次只能一人通过,还不能在桥上停留

7 LinkedTransferQueue

  • Transfer是一个集成了BlockingQueue的接口,并且增加了若干新方法,LinkedTransferQueue是其接口的实现类,定义了一个无界的队列,具有先进先出FIFO的特性
// 1. transfer(E e):若当前存在一个正在等待获取的消费者线程,即立刻移交之;否则,会插入当前元素e到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素。
// 2. tryTransfer(E e):若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数),使用该方法会即刻转移/传输对象元素e;若不存在,则返回false,并且不进入队列。这是一个不阻塞的操作。
// 3. tryTransfer(E e, long timeout, TimeUnit unit):若当前存在一个正在等待获取的消费者线程,会立即传输给它;否则将插入元素e到队列尾部,并且等待被消费者线程获取消费掉;若在指定的时间内元素e无法被消费者线程获取,则返回false,同时该元素被移除。
// 4. hasWaitingConsumer():判断是否存在消费者线程。
// 5. getWaitingConsumerCount():获取所有等待获取元素的消费线程数量。
// 6.size():因为队列的异步特性,检测当前队列的元素个数需要逐一迭代,可能会得到一个不太准确的结果,尤其是在遍历时有可能队列发生更改。
// 7.批量操作:类似于addAll,removeAll, retainAll, containsAll, equals, toArray等方法,API不能保证一定会立刻执行。因此,我们在使用过程中,不能有所期待,这是一个具有异步特性的队列。

8 LinkedBlockingDequeue

11 TaskQueue

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值