LinkedBlockingQueue底层原理结构

一、类结构

先看一下LinkedBlockingQueue类里面有哪些属性:

public class LinkedBlockingQueue<E>
        extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // 容量大小
    private final int capacity;

    // 元素个数
    private final AtomicInteger count = new AtomicInteger();

    // 头节点
    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();

    /**
     * 链表节点类
     */
    static class Node<E> {

        // 节点元素
        E item;

        // 后继节点
        Node<E> next;

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


二、结构图


三、初始化

LinkedBlockingQueue常用的初始化方法有两个:

  • 无参构造方法

  • 指定容量大小的有参构造方法

     /**
      * 无参构造方法
      */
     BlockingQueue<Integer> blockingQueue1 = new LinkedBlockingQueue<>();
    
     /**
      * 指定容量大小的构造方法
      */
     BlockingQueue<Integer> blockingQueue2 = new LinkedBlockingQueue<>(10);
    

再看一下对应的源码实现:

/**
 * 无参构造方法
 */
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}


/**
 * 指定容量大小的构造方法
 */
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) {
        throw new IllegalArgumentException();
    }
    // 设置容量大小,初始化头尾结点
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

可以看出LinkedBlockingQueue的无参构造方法使用的链表容量是Integer的最大值,存储大量数据的时候,会有内存溢出的风险,建议使用有参构造方法,指定容量大小。

有参构造方法还会初始化head (头节点)、head (尾节点),节点值为null。 LinkedBlockingQueue初始化的时候,不支持指定是否使用公平锁,只能使用非公平锁


四、存放数据源码

存放数据的方法有四个:


1、add方法源码

add()方法在数组满的时候,会抛出异常,底层基于offer()实现

/**
 * add方法入口
 *
 * @param e 元素
 * @return 是否添加成功
 */
public boolean add(E e) {
    if (offer(e)) {
        return true;
    } else {
        throw new IllegalStateException("Queue full");
    }
}


2、offer方法源码

offer()方法源码,其他放数据方法逻辑也是大同小异,都是在链表尾部插入。 offer()方法在队列满的时候,会直接返回false,表示插入失败

/**
 * offer方法入口
 *
 * @param e 元素
 * @return 是否插入成功
 */
public boolean offer(E e) {

    // 1. 判空,传参不允许为null
    if (e == null) {
        throw new NullPointerException();
    }

    // 2. 如果队列已满,则直接返回false,表示插入失败
    final AtomicInteger count = this.count;
    if (count.get() == capacity) {
        return false;
    }
    int c = -1;
    Node<E> node = new Node<E>(e);

    // 3. 获取put锁,并加锁
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        // 4. 加锁后,再次判断队列是否已满,如果未满,则入队
        if (count.get() < capacity) {
            enqueue(node);

            // 5. 队列个数加一
            c = count.getAndIncrement();

            // 6. 如果队列未满,则唤醒因为队列已满而等待存放数据的线程(用来补偿,不加也行)
            if (c + 1 < capacity) {
                notFull.signal();
            }
        }
    } finally {
        // 7. 释放锁
        putLock.unlock();
    }
    // 8. c等于0,表示插入前,队列为空,是第一次插入,需要唤醒因为队列为空而等待取数据的线程
    if (c == 0) {
        signalNotEmpty();
    }
    // 9. 返回是否插入成功
    return c >= 0;
}


3、enqueue方法源码

/**
 * 入队
 *
 * @param node 节点
 */
private void enqueue(LinkedBlockingQueue.Node<E> node) {
    // 直接追加到链表末尾
    last = last.next = node;
}


4、signalNotEmpty方法源码

/**
 * 唤醒因为队列为空而等待取数据的线程
 */
private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

offer()方法逻辑也很简单,追加元素到链表末尾,如果是第一次添加元素,就唤醒因为队列为空而等待取数据的线程


5、put方法源码

put()方法在队列满的时候,会一直阻塞,直到有其他线程取走数据,空出位置,才能添加成功

/**
 * put方法入口
 *
 * @param e 元素
 */
public void put(E e) throws InterruptedException {
    // 1. 判空,传参不允许为null
    if (e == null) {
        throw new NullPointerException();
    }
    int c = -1;
    Node<E> node = new Node<E>(e);
    // 2. 加可中断的锁,防止一直阻塞
    final ReentrantLock putLock = this.putLock;
    putLock.lockInterruptibly();
    final AtomicInteger count = this.count;
    try {
        // 3. 如果队列已满,就一直阻塞,直到被唤醒
        while (count.get() == capacity) {
            notFull.await();
        }
        // 4. 如果队列未满,则直接入队
        enqueue(node);
        c = count.getAndIncrement();
        // 5. 如果队列未满,则唤醒因为队列已满而等待放数据的线程(用来补偿,不加也行)
        if (c + 1 < capacity) {
            notFull.signal();
        }
    } finally {
        // 6. 释放锁
        putLock.unlock();
    }
    // 7. c等于0,表示插入前,队列为空,是第一次插入,需要唤醒因为队列为空而等待取数据的线程
    if (c == 0) {
        signalNotEmpty();
    }
}


五、取出数据源码

取出数据(取出数据并删除)的方法有四个:


1、remove 方法源码

remove()方法源码,如果队列为空,remove()会抛出异常

/**
 * remove方法入口
 */
public E remove() {

    // 1. 直接调用poll方法
    E x = poll();

    // 2. 如果取到数据,直接返回,否则抛出异常
    if (x != null) {
        return x;
    } else {
        throw new NoSuchElementException();
    }
}


2、poll 方法源码

poll()方法源码,是从链表头部弹出元素。 poll()方法在弹出元素的时候,如果队列为空,直接返回 null,表示弹出失败

/**
 * poll方法入口
 */
public E poll() {

    // 1、如果队列为空,则返回null
    final AtomicInteger count = this.count;
    if (count.get() == 0) {
        return null;
    }
    E x = null;
    int c = -1;

    // 2. 加锁
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 3. 如果队列不为空,则取出队头元素
        if (count.get() > 0) {
            x = dequeue();

            // 4. 元素个数减一
            c = count.getAndDecrement();

            // 5. 如果队列不为空,则唤醒因为队列为空而等待取数据的线程
            if (c > 1) {
                notEmpty.signal();
            }
        }
    } finally {
        // 6. 释放锁
        takeLock.unlock();
    }
    // 7. 如果取数据之前,队列已满,取数据之后队列肯定不满了,则唤醒因为队列已满而等待放数据的线程
    if (c == capacity) {
        signalNotFull();
    }
    return x;
}


3、dequeue 方法源码

/**
 * 取出队头元素
 */
private E dequeue() {
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h;
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}


4、signalNotFull 方法源码

/**
 * 唤醒因为队列已满,而阻塞等待存放数据的线程
 */
private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}


5、take方法源码

take()方法源码,如果队列为空,take()方法就一直阻塞,直到被唤醒

/**
 * take方法入口
 */
public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    // 1. 加可中断的锁,防止一直阻塞
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {

        // 2. 如果队列为空,就一直阻塞,直到被唤醒
        while (count.get() == 0) {
            notEmpty.await();
        }

        // 3. 如果队列不为空,则取出队头元素
        x = dequeue();

        // 4. 队列元素个数减一
        c = count.getAndDecrement();

        // 5. 如果队列不为空,则唤醒因为队列为空而等待取数据的线程
        if (c > 1) {
            notEmpty.signal();
        }
    } finally {
        // 6. 释放锁
        takeLock.unlock();
    }
    // 7. 如果取数据之前,队列已满,取数据之后队列肯定不满了,则唤醒因为队列已满而等待放数据的线程
    if (c == capacity) {
        signalNotFull();
    }
    return x;
}


六、查看数据源码

再看一下查看数据源码,查看数据,并不删除数据


1、peek方法源码

peek()方法源码,如果数组为空,直接返回 null

/**
 * peek方法入口
 */
public E peek() {
    // 1. 如果队列为空,则返回null
    if (count.get() == 0) {
        return null;
    }
    // 2. 加锁
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 3. 取出队头元素
        Node<E> first = head.next;
        if (first == null) {
            return null;
        } else {
            return first.item;
        }
    } finally {
        // 4. 释放锁
        takeLock.unlock();
    }
}


2、element方法源码

element()方法源码,如果队列为空,则抛出异常

/**
 * element方法入口
 */
public E element() {
    // 1. 调用peek方法查询数据
    E x = peek();
    // 2. 如果查到数据,直接返回
    if (x != null) {
        return x;
    } else {
        // 3. 如果没找到,则抛出异常
        throw new NoSuchElementException();
    }
}


七、总结

  • LinkedBlockingQueue实现了BlockingQueue接口,提供了四组放数据和读数据的方法,来满足不同的场景

  • LinkedBlockingQueue底层基于链表实现,支持从头部弹出数据,从尾部添加数据

  • LinkedBlockingQueue初始化的时候,如果不指定队列长度,默认长度是Integer最大值,有内存溢出风险,建议初始化的时候指定队列长度

  • LinkedBlockingQueue的方法是线程安全的,分别使用了读写两把锁,比ArrayBlockingQueue性能更好

posted @ 2025-05-28 21:14  jock_javaEE  阅读(40)  评论(0)    收藏  举报