LinkedBlockingQueue源码解读,附出队入队详细流程图

概述

LinkedBlockingQue是一个基于链表实现的可设置容量的无界(最大值Integer.MAX_VALUE)阻塞队列。队头的元素是插入时间最长的,队尾的元素是最新插入的。新的元素将会被插入到队列的尾部。

1、数据结构

基于链表,所以队列中至少有一个空元素,头结点不含元素。

2、原理

LinkedBlockingQueue中有两把锁,takeLock和putLock,即读写各一把锁。这就意味着只有读读、写写互斥,读写可以同时进行。
为了保证读写操作过程中队列元素个数的可见性和原子性,队列的元素变量count类型为AtomicInteger,且只在读写锁定过程中对其值进行增减,后面源码部分会涉及到。

3、 源码解读

3.1 变量

LinkedBlockingQueue阻塞队列中定义了入队锁putLock、出队锁takeLock及其相关的两个Condition对象notFullnotEmpty ,容量capacity、队头head、队尾last节点、队列中元素数量count
不再做详细介绍。

3.2 构造方法

LinkedBlockingQueue的构造方法有三个,分别如下:

  • 未设置容量大小,默认容量大小为Integer.MAX_VALUE
  • 设置容量大小为capacity,并且初始化队列。
  • Collection集合中的元素导入队列,其实用的就是put(E x)方法,后面将put方法时详述,这里不做详细介绍。

3.3 put(E x)入队方法

用于将元素插入队列尾部,队列已满会造成入队线程阻塞。先看流程图,然后再看源码。由于是为了让自己加深记忆,所以流程图画的比较细。
LinkedBlockingQueue源码解读,附出队入队详细流程图_第1张图片
然后再对照着看一下源码

public void put(E e) throws InterruptedException {
        //不允许元素为null
        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 {

            //如果队列已满,那么将该线程加入到Condition的等待队列中
            while (count.get() == capacity) {
                notFull.await();
            }
            //将节点入队
            enqueue(node);
            //得到插入之前队列的元素个数
            c = count.getAndIncrement();
            //如果还可以插入元素,那么释放等待的入队线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            //解锁
            putLock.unlock();
        }
        //通知出队线程队列非空
        if (c == 0)
            signalNotEmpty();
    }

3.4 take(E x)出队方法

用于取出队尾元素,如果队列为空,会在成出队线程阻塞。同样的,想看流程图在看源码。
LinkedBlockingQueue源码解读,附出队入队详细流程图_第2张图片

public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //获取takeLock锁
        takeLock.lockInterruptibly();
        try {
            //如果队列为空,那么加入到notEmpty条件的等待队列中
            while (count.get() == 0) {
                notEmpty.await();
            }
            //得到队头元素
            x = dequeue();
            //得到取走一个元素之前队列的元素个数
            c = count.getAndDecrement();
            //如果队列中还有数据可取,释放notEmpty条件等待队列中的第一个线程
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        //如果队列中的元素从满到非满,通知put线程
        if (c == capacity)
            signalNotFull();
        return x;
    }

3.5 其他方法

其他方法都类似于读写方法的原理,所以不再赘述。有兴趣的可以去看看源码。由于一般我们用阻塞队列都是用于多线程并发场景下,所以其实只要了解出队入队操作就基本上可以,其他方法很少用到,也不建议花太多时间去研究。

总结

  • 元素不允许为空,为空会抛NullPointException异常;
  • 可以设置队列长度,也可以是无界队列,对大值为Integer.MAX_VALUE;
  • 基于链表,底层数据结构为链表,初始化后,对头会有一个空元素;
  • 读写均加锁,所以安全类;
  • 读写两把锁,所以读写可以同时进行,相对ArrayBlockingQueue来说吞吐量会大一点;
  • 每次入队操作,都会初始化一个Node对象,所以频繁的入队会产生较多的新生代垃圾;

你可能感兴趣的:(并发编程,队列,链表,数据结构,多线程,java)