Java并发集合之LinkedBlockingQueue使用与原理

LinkedBlockingQueue简介

LinkedBlockingQueue是一个基于单向链表实现的线程安全的阻塞队列,按照FIFO(先进先出)的原则,在队列尾部插入新数据,从队列头部取出数据,同时为了防止队列无限膨胀,LinkedBlockingQueue还支持设置容量,默认容量为Integer.MAX_VALUE

LinkedBlockingQueue特性

  • 继承AbstractQueue,是一个FIFO的队列
  • 线程安全,多线程竞争时会阻塞等待,通过ReentrantLock实现
  • 通过单向链表实现,并且支持设置链表容量
  • 分为读锁和写锁,读取和写入不互斥

LinkedBlockingQueue的数据结构

	// 链表头节点
	transient Node<E> head;

    // 链表尾节点
    private transient Node<E> last;

    // 读取数据锁
    private final ReentrantLock takeLock = new ReentrantLock();

    // 阻塞等待读取数据
    private final Condition notEmpty = takeLock.newCondition();

    // 写入数据锁
    private final ReentrantLock putLock = new ReentrantLock();

    // 阻塞等待写入数据
    private final Condition notFull = putLock.newCondition();

Node类定义

static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

LinkedBlockingQueue部分源码解析

构造方法

public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

构造方法初始化容量,同时创建一个空节点,此时头节点和尾节点为同一节点

变更方法

  • put方法
public void put(E e) throws InterruptedException {
		// 数据不能为空
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        // 创建新增数据的链表节点
        Node<E> node = new Node<E>(e);
        // 获取写入数据锁
        final ReentrantLock putLock = this.putLock;
        // 获取当前已有的元素数
        final AtomicInteger count = this.count;
        // 加锁并且当前线程中断时抛出中断异常
        putLock.lockInterruptibly();
        try {
            // 当达到最大容量时无限等待队列空闲空间
            while (count.get() == capacity) {
                notFull.await();
            }
            // 向队列中增加元素
            enqueue(node);
            // 设置队列容量+1
            c = count.getAndIncrement();
            // 如果未超过最大容量则唤醒写入等待的线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        // c==0表示队列中有1条数据,唤醒所有等待取数据的线程
        if (c == 0)
            signalNotEmpty();
    }
  • enqueue方法,向链表中添加元素的方法
private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
    }

这里的写法,实际相当于:

last.next = node;
last = node;
  • take方法
public E take() throws InterruptedException {
        E x;
        int c = -1;
        // 获取当前队列中元素总数
        final AtomicInteger count = this.count;
        // 获取当前队列读取数据的锁
        final ReentrantLock takeLock = this.takeLock;
        // 加锁并且当前线程中断时抛出中断异常
        takeLock.lockInterruptibly();
        try {
        	// 当前队列元素总数为0时无限等待
            while (count.get() == 0) {
                notEmpty.await();
            }
            // 获取队列头部的数据
            x = dequeue();
            // 当前队列元素总数减一,注意c为减一之前的值
            c = count.getAndDecrement();
            // 取出元素后如果当前队列的元素总数大于0则唤醒所有等待消费的线程
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        // 如果取出元素前的队列元素总数等于队列的容量,则唤醒所有等待写入的线程,因为此时已经取出一个元素有空余的空间可以进行写入
        if (c == capacity)
            signalNotFull();
        return x;
    }
  • dequeue方法
private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        // 获取队列底层链表的头节点,链表头节点为空
        Node<E> h = head;
        // 获取队列的第一个元素,因为链表头节点为空,因此链表的第一个有效节点为h.next
        Node<E> first = h.next;
        // 删除原始头节点的引用
        h.next = h; // help GC
        // 设置head节点为获取到的第一个有效节点
        head = first;
        // 获取第一个有效节点存储的元素
        E x = first.item;
        // 将头节点的元素设置为null
        first.item = null;
        return x;
    }

注意:因为在LinkedBlockingQueue初始化的时候就会创建一个链表的表头和表尾节点,是一个空节点,因此在实际向LinkedBlockingQueue添加数据时是从链表的第二个元素开始存储数据的,所以取数据也是从表头节点的下一节点取数据

  • signalNotFull方法
private void signalNotFull() {
		// 获取写入数据锁
        final ReentrantLock putLock = this.putLock;
        // 加锁
        putLock.lock();
        try {
        	// 唤醒写入等待的线程
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }

遍历方法

  • iterator方法
public Iterator<E> iterator() {
        return new Itr();
    }
  • Itr类实现
private class Itr implements Iterator<E> {
		// 当前节点
        private Node<E> current;
        // 上一次返回节点
        private Node<E> lastRet;
        // 当前元素
        private E currentElement;

        Itr() {
        	// 同事对队列的读取和写入同时加锁
            fullyLock();
            try {
            	// 当前节点为链表的第一个有效节点,即head的下一个节点
                current = head.next;
                if (current != null)
                	// 设置当前元素
                    currentElement = current.item;
            } finally {
                fullyUnlock();
            }
        }

        public boolean hasNext() {
            return current != null;
        }

		// 获取需要遍历的下一节点
        private Node<E> nextNode(Node<E> p) {
            for (;;) {
                Node<E> s = p.next;
                // 如果s==p则表示该节点对应的元素已被取出,那么直接取当前链表的第一个有效元素
                if (s == p)
                    return head.next;
                // s==null表示遍历结束,s.item不为null则返回当前节点
                if (s == null || s.item != null)
                    return s;
                p = s;
            }
        }

		// 返回当前元素值
        public E next() {
        	// 对当前队列增加读写锁
            fullyLock();
            try {
                if (current == null)
                    throw new NoSuchElementException();
                // 获取当前节点元素
                E x = currentElement;
                // 将当前节点赋值给上次获取节点
                lastRet = current;
                // 获取下一个需要遍历的节点
                current = nextNode(current);
                // 获取下一个需要遍历的节点对应的元素
                currentElement = (current == null) ? null : current.item;
                return x;
            } finally {
                fullyUnlock();
            }
        }

		// 删除元素
        public void remove() {
            if (lastRet == null)
                throw new IllegalStateException();
            // 给队列的读取和写入加锁
            fullyLock();
            try {
            	// 获取遍历的前一个节点
                Node<E> node = lastRet;
                lastRet = null;
                // 从头节点遍历整个链表,删除指定的节点
                for (Node<E> trail = head, p = trail.next;
                     p != null;
                     trail = p, p = p.next) {
                    if (p == node) {
                        unlink(p, trail);
                        break;
                    }
                }
            } finally {
                fullyUnlock();
            }
        }
    }
  • unlink方法
void unlink(Node<E> p, Node<E> trail) {
		// 置空p节点对应的元素
        p.item = null;
        // 前置节点的next设置为当前待删除节点的next
        trail.next = p.next;
        // 如果p是尾节点,则将trail设置为尾节点
        if (last == p)
            last = trail;
        // 如果删除元素前队列容量已满,则唤醒所有需要写入的线程
        if (count.getAndDecrement() == capacity)
            notFull.signal();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值