025_java.util.concurrent.LinkedBlockingQueue

继承体系

image.png
可以看到和ArrayBlockingQueue的继承体系并没有太大区别。相比较ArrayBlockingQueue,就类似于LinkedList对比ArrayList。ArrayBlockingQueued底层使用的是数组实现,而LinkedBlockingQueue则是使用链表进行实现。

重要属性

// 容量
private final int capacity;

// 元素数量,与ArrayBlockingQueue不同,使用的是原子类
private final AtomicInteger count = new AtomicInteger();

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

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

// 可以看到这个地方有两个ReentrantLock,分别是takeLock,putLock
// 这意味着入队列与出队列所使用的锁不一样,互相不会阻塞
private final ReentrantLock takeLock = new ReentrantLock();

// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
private final Condition notEmpty = takeLock.newCondition();

// 放锁
private final ReentrantLock putLock = new ReentrantLock();

// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
private final Condition notFull = putLock.newCondition();

构造方法

public LinkedBlockingQueue() {
    // 如果没传容量,就使用最大int值初始化其容量
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    // 初始化head和last指针为空值节点
    last = head = new Node<E>(null);
}

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();
    }
}

可以看到LinkedBlockingQueue默认使用Int的最大值,也就是默认是无界队列。可以主动传递容量值进行限定。除此之外也可以使用集合数据进行初始化,同ArrayBlokingQueue初始化类似,其内部加了锁,防止重排序或者可见性带来的线程安全问题

重要方法

入队方法

  • add(E e) 调用offer(e)如果成功返回true,如果失败抛出异常
  • offer(E e) 返回true/false
  • put(E e) 不断循环调用入队方法,直到成功
  • offer(E e, long timeout, TimeUnit unit) 不断循环调用入队方法,unit时间后放弃
// LinkedBlockingQueue 未覆盖此方法,使用AbstractQueue默认实现
public boolean add(E e) {
    // 调用子类offer方法
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    // 达到容量直接返回false
    if (count.get() == capacity)
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        // 锁住之后再次对比是否可以入队,不能入队则返回-1表达入队失败
        if (count.get() < capacity) {
            // 入队
            enqueue(node);
            // 大小加一,注意这里的getAndIncrement。比如count是1的话,c是等于1的
            c = count.getAndIncrement();
            // 判断是否没有达到容量,没有达到则唤醒
            // 这里为啥要唤醒一下呢?
            // 因为可能有很多线程阻塞在notFull这个条件上的
            // 而取元素时只有取之前队列是满的才会唤醒notFull
            // 为什么队列满的才唤醒notFull呢?
            // 因为唤醒是需要加putLock的,这是为了减少锁的次数
            // 所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    // c == 0意味着count原先是0,经过上面的操作之后队列内就存在元素了
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    if (e == null) throw new NullPointerException();
    // 计算到超出时间限定的时间长度
    long nanos = unit.toNanos(timeout);
    int c = -1;
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    // 尝试加锁
    putLock.lockInterruptibly();
    try {
        // 如果达到容量则一直循环
        while (count.get() == capacity) {
            // 如果已经超时则返回入队失败
            if (nanos <= 0)
                return false;
            // 等待超时时长,等到条件符合的时候返回剩余时长,重新进入循环
            nanos = notFull.awaitNanos(nanos);
        }
        // 入队
        enqueue(new Node<E>(e));
        c = count.getAndIncrement();
        // 判定是否满了
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // c== 0 意味着入队成功,通知唤醒
    if (c == 0)
        signalNotEmpty();
    return true;
}

public void put(E e) throws InterruptedException {
    // 不允许null元素
    if (e == null) throw new NullPointerException();
    int c = -1;
    // 新建一个节点
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    // 使用put锁加锁
    putLock.lockInterruptibly();
    try {
        // 如果队列满了,就阻塞在notFull条件上
        // 等待被其它线程唤醒
        while (count.get() == capacity) {
            notFull.await();
        }
        // 队列不满了,就入队
        enqueue(node);
        // 队列长度加1
        c = count.getAndIncrement();
        // 如果现队列长度如果小于容量
        // 就再唤醒一个阻塞在notFull条件上的线程
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        // 释放锁
        putLock.unlock();
    }
    // 如果原队列长度为0,现在加了一个元素后立即唤醒notEmpty条件
    if (c == 0)
        signalNotEmpty();
}

private void enqueue(Node<E> node) {
    // 直接加到last后面
    last = last.next = node;
}    

private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    // 加take锁
    takeLock.lock();
    try {
        // 唤醒notEmpty条件
        notEmpty.signal();
    } finally {
        // 解锁
        takeLock.unlock();
    }
}

出队方法

出队也有4个方法:

  • #poll() 方法:获取并移除此队列的头,如果此队列为空,则返回 null 。
  • #poll(long timeout, TimeUnit unit) 方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素
  • #take() 方法:获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
  • #remove(Object o) 方法:从此队列中移除指定元素的单个实例(如果存在)。
public E remove() {
    // 调用poll完成出队
    E x = poll();
    if (x != null)
        return x;
    else
        // 如果没有数据则抛出异常
        throw new NoSuchElementException();
}

public E poll() {
    final AtomicInteger count = this.count;
    // 队内无数据则返回null
    if (count.get() == 0)
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        if (count.get() > 0) {
            // 执行出队
            x = dequeue();
            // 获得出队前的个数
            c = count.getAndDecrement();
            // 对内还存在数据,执行唤醒
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    // c是出队前的数据,这个时候已经取出一个,执行通知
    if (c == capacity)
        signalNotFull();
    return x;
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    E x = null;
    int c = -1;
    long nanos = unit.toNanos(timeout);
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

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();
        // 如果取之前队列长度大于1,则唤醒notEmpty
        if (c > 1)
            notEmpty.signal();
    } finally {
        // 释放锁
        takeLock.unlock();
    }
    // 如果取之前队列长度等于容量
    // 则唤醒notFull
    if (c == capacity)
        signalNotFull();
    return x;
}

private E dequeue() {
    // head节点本身是不存储任何元素的
    // 这里把head删除,并把head下一个节点作为新的值
    // 并把其值置空,返回原来的值
    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
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}

总结

LinkedBlockingQueue底层使用链表来维护队列,存在head与last指针指向链头与链尾。其内部存在两个锁分别控制出队与入队操作。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值