jdk源码分析之 LinkedBlockingQueue

LinkedBlockingQueue简介:
LinkedBlockingQueue 中文叫做链表阻塞队列,单从名字就可以得知其底层的实现是由链表来实现的。其原理为使用链表+锁+迭代器来完成。有链表实现的阻塞队列,队列的尾为链表的尾节点,队首为链表的头节点。在入对和出对都由ReentrantLock实现的takeLock和putLock来实现加锁从而到达,线性安区和队列阻塞。
其关心类图如下:

从类图中可知LinkedBlockingQueue->BlockingQueue -> Queue -> Collection,BlockingQueue 和 Queue 都是定义了一些queue基本操作,具体实现由子类实现,同时实现了。
其成员变量

//  链表存放queue数据
    static class Node<E> {
        E item;

        
        Node<E> next;

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

    /**  如果传过来的队列的大小为空则默认是nteger.MAX_VALUE 最大值 */
    private final int capacity;

    /** 队列的包含的个数*/
    private final AtomicInteger count = new AtomicInteger();

    /**
     * head.item == null
     *  链表头
     */
    transient Node<E> head;

    /**
     * 链表尾节点
     * Invariant: last.next == null
     */
    private transient Node<E> last;

    /**
    *  锁 begin  
    *  take时锁
    */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** 基于AQS同步机制的条件队列*/
    private final Condition notEmpty = takeLock.newCondition();

    /** put时锁,takeLock和putLock两把锁,主要是为了put和takeLock可以同时进行  */
    private final ReentrantLock putLock = new ReentrantLock();

    /** put的条件队列 */
    private final Condition notFull = putLock.newCondition();
    // 锁结束

常用的 方法:

  • 入对操作:
    add 队列满的时候抛出异常;
    offer 队列满的时候返回 false。
  • 查看并出对操作:
    remove 队列空的时候抛异常;
    poll 队列空的时候返回 null。
  • 只查看不删除操作:
    element 队列空的时候抛异常;
    peek 队列空的时候返回 null。

这六个方法的区别:
遇到队列满或空的时候,抛异常,如 add、remove、element;
遇到队列满或空的时候,返回特殊值,如 offer、poll、peek。

其构造器:

  • 无参构造器
  • 传入一个capacity容量的构造器
  • 传入一个集合的构造器。
    源码如下:
/**
     * 一个无参构造方法,此时queue的大小为Integer最大值
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    /**
     *  有参构造器,此时队列的大小为传过来的capacity的大小
     *
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

    /**
     * 传入一个Collection对象,此时queue的大小为Integer最大值
     */
    public LinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }

从构造器中可以发现:初始化时,容量大小是不会影响性能的,只影响在后面的使用,因为初始化队列太小,容易导致没有放多少就会报队列已满的错误;并且传入一个集合构造器时每次都会检测是否还有空间,实现不优雅,每次循环都检测空间爱是否足够。
入对分析以put为例:
put 方法在碰到队列满的时候,会一直阻塞下去,直到队列不满时,并且自己被唤醒时,才会继续去执行,

/ 把e新增到队列的尾部。
// 如果有可以新增的空间的话,直接新增成功,否则当前线程陷入等待
public void (E e) throws InterruptedException {
    // e 为空,抛出异常
    if (e == null) throw new NullPointerException();
    // 预先设置 c 为 -1,约定负数为新增失败
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    // 设置可中断锁
    putLock.lockInterruptibly();
    try {
        // 队列满了
        // 当前线程阻塞,等待其他线程的唤醒(其他线程 take 成功后就会唤醒此处被阻塞的线程)
        while (count.get() == capacity) {
            // await 无限等待
            notFull.await();
        }

        // 队列没有满,直接新增到队列的尾部
        enqueue(node);

        // 新增计数赋值,注意这里 getAndIncrement 返回的是旧值
        // 这里的 c 是比真实的 count 小 1 的
        c = count.getAndIncrement();

        // 如果链表现在的大小 小于链表的容量,说明队列未满
        // 可以尝试唤醒一个 put 的等待线程
        if (c + 1 < capacity)
            notFull.signal();

    } finally {
        // 释放锁
        putLock.unlock();
    }
    // c==0,代表队列里面有一个元素
    // 会尝试唤醒一个take的等待线程
    if (c == 0)
        signalNotEmpty();
}
// 入队,把新元素放到队尾
private void enqueue(Node<E> node) {
    last = last.next = node;
}

出对分析:(原理大致同入对)
整体流程和 put 很相似,都是先上锁,然后从队列的头部拿出数据,如果队列为空,会一直阻塞到队列有值为止。

// 阻塞拿数据
public E take() throws InterruptedException {
    E x;
    // 默认负数,代表失败
    int c = -1;
    // count 代表当前链表数据的真实大小
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        // 空队列时,阻塞,等待其他线程唤醒
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 非空队列,从队列的头部拿一个出来
        x = dequeue();
        // 减一计算,注意 getAndDecrement 返回的值是旧值
        // c 比真实的 count 大1
        c = count.getAndDecrement();
        
        // 如果队列里面有值,从 take 的等待线程里面唤醒一个。
        // 意思是队列里面有值啦,唤醒之前被阻塞的线程
        if (c > 1)
            notEmpty.signal();
    } finally {
        // 释放锁
        takeLock.unlock();
    }
    // 如果队列空闲还剩下一个,尝试从 put 的等待线程中唤醒一个
    if (c == capacity)
        signalNotFull();
    return x;
}
// 队头中取数据
private E dequeue() {
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h; // help GC
    head = first;
    E x = first.item;
    first.item = null;// 头节点指向 null,删除
    return x;
}

查看peek
接把队列头的数据拿出来即可

// 查看并不删除元素,如果队列为空,返回 null
public E peek() {
    // count 代表队列实际大小,队列为空,直接返回 null
    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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值