LinkedBlockingQueue

1.LinkedBlockingQueue是什么 ?

​ LinkedBlockingQueue 也是使用单向链表实现的,可以当做无界队列也可以当做有界队列来使用。可以通过构造函数指定界限,,LinkedBlockingQueue默认是有界限的,默认队列容量为Integer.MAX_VALUE,用户也可以自己指定容量,所以从一定程 度上可以说 LinkedBlockingQueue 是有界阻塞队列。

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

2.LinkedBlockingQueue源码解析

// 队列容量
private final int capacity;

// 当前队列元素个数
private final AtomicInteger count = new AtomicInteger(0);

// 队头
private transient Node<E> head;

// 队尾
private transient Node<E> last;

// take, poll, peek 等读操作的方法需要获取到这个锁
private final ReentrantLock takeLock = new ReentrantLock();

// 当队列为空时,执行出队操作(比如 take )的线程会被放入这个条件队列进行等待
private final Condition notEmpty = takeLock.newCondition();

// put, offer 等写操作的方法需要获取到这个锁
private final ReentrantLock putLock = new ReentrantLock();

// 当队列满时 , 执行进队操作( 比如put)的线程会被放入这个条件队71]进行等待
private final Condition notFull = putLock.newCondition();
2.1 0ffer操作

向队列尾部插入一个元素,如果队列中有空闲则插入成功后返回 true,如果队列己满 则丢弃当前元素然后返回 false。 该方法是非阻塞 的 。

public boolean offer(E e) {
  		//空元素则抛出空指针异常
        if (e == null) throw new NullPointerException();
  		//如采当前队列满则丢弃将要放入的元素, 然后返回 fals
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;
  		//构造新节点,获取putLock独占锁
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
          //如采队列不满则进队列,并递增元素计数
            if (count.get() < capacity) {
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
   // 如果 c == 0,那么代表队列在这个元素入队前是空的(不包括head空节点),
    // 那么所有的读线程都在等待 notEmpty 这个条件,等待唤醒,这里做一次唤醒操作
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

//入队列就是将 last 属性指向这个新元素,并且让原队尾的 next 指向这个元素
 private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
    }
2.2put操作

​ 向队列尾部插入一个元素,如果队列中有空 闲则插入后直接返回,如果队列己满则阻 塞当前线程,直到队列有空闲插入成功后返回。如果在阻塞时被其他线程设置了中断标志, 则 被阻塞线程会抛出 InterruptedException 异常而返回。另外,如果 e 元素为 null 则 抛出 NullPointerException 异 常。

 public void put(E e) throws InterruptedException {
   //如果为空元素则抛出空指针异常
        if (e == null) throw new NullPointerException();
    如果你纠结这里为什么是 -1,这就是个标识成功、失败的标志而已。
       int c = -1;
   //新节点,并获取独占锁 
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
           //如果队列满,等待 notFull 的条件满足。
            while (count.get() == capacity) {
                notFull.await();
            }
           // 入队
            enqueue(node);  
       // count 原子加 1,c 还是加 1 前的值
            c = count.getAndIncrement();
          // 如果这个元素入队后,还有至少一个槽可以使用,调用 notFull.signal() 唤醒等待线程。
           // 哪些线程会等待在 notFull 这个 Condition 上呢?
          //则唤醒 notFull 的条件队列里面,因为调用了 notFull 的 await 操作(比如执行 put 方法而队列满了的时候)而被阻塞的一个 线程,因为队列现在有空闲所以这里可 以提前唤醒一个入队线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
           // 如果 c == 0,那么代表队列在这个元素入队前是空的(不包括head空节点),
    // 那么所有的读线程都在等待 notEmpty 这个条件,等待唤醒,这里做一次唤醒操作
            signalNotEmpty();
    }

// 元素入队后,如果需要,调用这个方法唤醒读线程来读
private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}
2.3take操作

​ 获取当前队列头部元素并从队列里面移除它。 如果队列为空则阻塞当前线程直到队列不为空然 后返回元 素 ,如 果在 阻 塞 时 被其 他 线程设置 了中 断 标 志 , InterruptedException异常而返回

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    // 获取锁
    takeLock.lockInterruptibly();
    try {
        //当前队列为空则阻塞拉起 ,等待 notEmpty 这个条件满足再继续执行
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 出队并递减计数
        x = dequeue();
        // count 进行原子减 1
        c = count.getAndDecrement();
        // 如果这次出队后,队列中至少还有一个元素,那么调用 notEmpty.signal() 唤醒其他的读线程
        if (c > 1)
            notEmpty.signal();
    } finally {
        // 出队后释放掉 takeLock
        takeLock.unlock();
    }
    // 如果 c == capacity,那么说明在这个 take 方法发生的时候,队列是满的
    // 既然出队了一个,那么意味着队列不满了,唤醒写线程去写
    if (c == capacity)
        signalNotFull();
    return x;
}
// 取队头,出队
private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    // 之前说了,头结点是空的
    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.signal();
    } finally {
        putLock.unlock();
    }
}
2.4remove 操作
public boolean remove(Object o) {
        if (o == null) return false;
  //双重加锁
        fullyLock();
        try {
          //遍历 找到则删除并返回true
            for (Node<E> trail = head, p = trail.next;
                 p != null;
                 trail = p, p = p.next) {
                if (o.equals(p.item)) {
                    unlink(p, trail);
                    return true;
                }
            }
          //找不到则返回 false
            return false;
        } finally {
          //释放双重锁 。
            fullyUnlock();
        }
    }

//寻找要删除的元素,找不到则直接返回 false,找到则执行 unlink 操作 。 unlik 操作的代码如下。
  void unlink(Node<E> p, Node<E> trail) {
        // assert isFullyLocked();
        // p.next is not changed, to allow iterators that are
        // traversing p to maintain their weak-consistency guarantee.
        p.item = null;
        trail.next = p.next;
        if (last == p)
            last = trail;
    如果当前队列满 ,则删除后,也不忘记唤醒等待的线程
        if (count.getAndDecrement() == capacity)
            notFull.signal();
    }

3.总结

LinkedBlockingQueue 的内部是通过单向链表实现的,使用头、尾节点来进行入队和出队操作,也就是入队操作都是对尾节点进行操作,出队操作都是对头节点进行操作 。对头、尾节点的操作分别使用了单独的独占锁从而保证了原子性,所以出队和入队操作是可以同时进行的 。 另外对头 、 尾节点的独占锁都配备了一个条件队 列,用来存放被阻塞的线程,并结合入队、出队操作实现了 一个生产消费模型 。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值