LinkedBolckingQueue

LinkedBolckingQueue

  LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。

原理:
  LinkedBlockingQueue底层维持着两把锁,putLock用于put操作添加元素,takeLock用于take操作移除元素,同一时刻只能有一个线程进行put操作,其他想要执行put操作的线程会被阻塞,同时可以有另一个线程执行take操作,其他想要执行take操作的线程也一样会被阻塞;
  简单来说,就是put(take)操作都是串行的,同一时刻只能有一个线程去执行put(take)操作,但两个一个执行put操作的线程和一个执行take操作的线程可以同时进行,互不干涉,为了维持线程安全,LinkedBlockingQueue中使用了AtomicInteger类型的变量count来表示队列中的元素个数;

1、继承关系

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

2、属性

private final int capacity; //容量,默认情况时则为Integer.MAX_VALUE

private final AtomicInteger count = new AtomicInteger(0);  //并发的原子操作类,指当前队列中元素的个数

private transient Node<E> head; //头节点,head.item == null

private transient Node<E> last; //尾结点,last.next == null

//队列同步相关属性,在声明时已经被初始化
private final ReentrantLock takeLock = new ReentrantLock(); //用于take操作的重入锁
private final Condition notEmpty = takeLock.newCondition(); //takeLock的Condition实例
private final ReentrantLock putLock = new ReentrantLock(); //用于put操作的重入锁
private final Condition notFull = putLock.newCondition(); //putLock的Condition实例

看JDK源码可以发现LinkedBlockingQueue内部由链表实现的队列至少有一个节点,头节点不含元素,head.item == null,尾结点last.next == null
在这里插入图片描述
3、构造函数

  • 使用无参构造时容量是int类型的最大值;
  • 队列对外表现为空时,也至少包含一个节点(head);
  • LinkedBolckingQueue同样不支持添加null元素;
//用默认长度Integer.MAX_VALUE来初始化LinkedBlockingQueue的大小
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
//通过capacity来初始化LinkedBlockingQueue的大小
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}
//用集合来初始化LinkedBlockingQueue
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();
    }
}

4、put() 添加元素

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException(); //待添加元素为null时抛出异常
    int c = -1;
    Node<E> node = new Node(e); //用待添加元素新建一个节点
    final ReentrantLock putLock = this.putLock; //获取putLock锁
    final AtomicInteger count = this.count;
    //显性的添加可中断锁
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) { //如果队列已满,将当前线程添加到Condition实例notFull的等待队列中
            notFull.await();
        }
        //队列没满则将新建的节点入队列
        enqueue(node);
        
        //调用getAndIncrement()后返回旧值,c为添加节点之前队列中的元素个数count,但count已经进行了原子性的+1操作
        c = count.getAndIncrement();
        if (c + 1 < capacity) { //c+1 < capacity说明队列未满还可以添加元素,释放等待入队列(put操作)的线程
             notFull.signal();
        } 
    } finally {
        putLock.unlock(); //显性的释放锁
    }
    if (c == 0) { //c == 0时,就是添加元素前队列中元素的个数为0,那么添加之后的个数count为1,也就是说队列非空,通知等待出队列(take操作)的线程
        signalNotEmpty();
    }     
}
==================================================================================
private void enqueue(Node<E> node) {
    last = last.next = node;  //将节点node添加在队尾
}
private void signalNotEmpty() { //通知消费者队列不为空了,
    final ReentrantLock takeLock = this.takeLock; //获取takeLock锁
    //加锁
    takeLock.lock();
    try {
    	//释放一个在notEmpty等待队列中的线程
        notEmpty.signal();
    } finally {
        takeLock.unlock(); //释放锁
    }
}

简单来说put()就是:

1)插入元素时

  • 若当队列满,就将当前线程加入到notFull的条件等待队列中,
  • 当队列不满,则添加元素,
  • 若添加之后队列依然未满则通知notFull,

2)队列非空则通知take线程

5、take() 移除元素

 public E take() throws InterruptedException {
     E x;
     int c = -1;
     final AtomicInteger count = this.count;
     final ReentrantLock takeLock = this.takeLock; //获取takeLock锁
     takeLock.lockInterruptibly(); //添加可中断锁
     try {
         while (count.get() == 0) { //当队列为空时,当前线程加入notFull的条件等待队列中
             notEmpty.await();
        }
        x = dequeue(); //得到队头元素
		//调用getAndDecrement()后返回旧值,c为删除节点之前队列中的元素个数count,但此处count已经进行了原子性的-1操作
        c = count.getAndDecrement();
        if (c > 1) { //c为删除元素之前队列中的元素个数,说明队列中至少还有一个元素,释放notEmpty条件等待队列中的一个线程
            notEmpty.signal();
        }
    } finally {
        takeLock.unlock(); //释放锁
    }
    //删除操作之前c == capacity,说明队列未满,通知等待入队的put线程
    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;
}

简单来说take()就是:

1)移除元素时

  • 当队列为空时,将当前线程加入到notEmpty的条件等待队列中,
  • 当队列不为空则移除一个元素,
  • 若此时队列依然不为空则通知notEmpty,

2)如果队列非满,就通知put线程

猜你喜欢

转载自blog.csdn.net/Daria_/article/details/89505181