阻塞队列
生产者 消费者彼此之间不会直接通讯的,而是通过一个容器(队列)进行通讯。
所以生产者生产完数据后扔到容器中,不用等待消费者来处理。
消费者不需要去找生产者要数据,直接从容器中获取即可。
add(E) // 添加数据到队列,如果队列满了,无法存储,抛出异常,实际还是调用的offer方法 offer(E) // 添加数据到队列,如果队列满了,返回false offer(E,timeout,unit) // 添加数据到队列,如果队列满了,阻塞timeout时间,如果阻塞一段时间,依然没添加进入,返回false put(E) // 添加数据到队列,如果队列满了,挂起线程,等到队列中有位置,再扔数据进去,死等!
消费者取数据方法
remove() // 从队列中移除数据,如果队列为空,抛出异常,实际调用poll方法 poll() // 从队列中移除数据,如果队列为空,返回null,没有数据 poll(timeout,unit) // 从队列中移除数据,如果队列为空,挂起线程timeout时间,等生产者扔数据,再获取 take() // 从队列中移除数据,如果队列为空,线程挂起,一直等到生产者扔数据,再获取,死等
lock = 就是一个ReentrantLock count = 就是当前数组中元素的个数 iterms = 就是数组本身 # 基于putIndex和takeIndex将数组结构实现为了队列结构 putIndex = 存储数据时的下标 takeIndex = 取数据时的下标 notEmpty = 消费者挂起线程和唤醒线程用到的Condition(看成sync的wait和notify) notFull = 生产者挂起线程和唤醒线程用到的Condition(看成sync的wait和notify)
public boolean offer(E e) { // 要求存储的数据不允许为null,为null就抛出空指针 checkNotNull(e); // 当前阻塞队列的lock锁 final ReentrantLock lock = this.lock; // 为了保证线程安全,加锁 lock.lock(); try { // 如果队列中的元素已经存满了, if (count == items.length) // 返回false return false; else { // 队列没满,执行enqueue将元素添加到队列中 enqueue(e); // 返回true return true; } } finally { // 操作完释放锁 lock.unlock(); } } //========================================================== private void enqueue(E x) { // 拿到数组的引用 final Object[] items = this.items; // 将元素放到指定位置 items[putIndex] = x; // 对inputIndex进行++操作,并且判断是否已经等于数组长度,需要归位 if (++putIndex == items.length) // 将索引设置为0 putIndex = 0; // 元素添加成功,进行++操作。 count++; // 将一个Condition中阻塞的线程唤醒。 notEmpty.signal(); }
public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) // await方法一直阻塞,直到被唤醒或者中断标记位 notFull.await(); enqueue(e); } finally { lock.unlock(); } }
消费者方法实现原理
// 拉取数据 public E poll() { // 加锁操作 final ReentrantLock lock = this.lock; lock.lock(); try { // 如果没有数据,直接返回null,如果有数据,执行dequeue,取出数据并返回 return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } //========================================================== // 取出数据 private E dequeue() { // 将成员变量引用到局部变量 final Object[] items = this.items; // 直接获取指定索引位置的数据 E x = (E) items[takeIndex]; // 将数组上指定索引位置设置为null items[takeIndex] = null; // 设置下次取数据时的索引位置 if (++takeIndex == items.length) takeIndex = 0; // 对count进行--操作 count--; // 迭代器内容,先跳过 if (itrs != null) itrs.elementDequeued(); // signal方法,会唤醒当前Condition中排队的一个Node。 // signalAll方法,会将Condition中所有的Node,全都唤醒 notFull.signal(); // 返回数据。 return x; }
// 因为是链表,没有像数组那样的length属性,基于AtomicInteger来记录长度 private final AtomicInteger count = new AtomicInteger(); // 链表的头,取 transient Node<E> head; // 链表的尾,存 private transient Node<E> last; // 消费者的锁 private final ReentrantLock takeLock = new ReentrantLock(); // 消费者的挂起操作,以及唤醒用的condition private final Condition notEmpty = takeLock.newCondition(); // 生产者的锁 private final ReentrantLock putLock = new ReentrantLock(); // 生产者的挂起操作,以及唤醒用的condition private final Condition notFull = putLock.newCondition();
生产者方法实现原理
public boolean offer(E e) { // 非空校验 if (e == null) throw new NullPointerException(); // 拿到存储数据条数的count final AtomicInteger count = this.count; // 查看当前数据条数,是否等于队列限制长度,达到了这个长度,直接返回false if (count.get() == capacity) return false; // 声明c,作为标记存在 int c = -1; // 将存储的数据封装为Node对象 Node<E> node = new Node<E>(e); // 获取生产者的锁。 final ReentrantLock putLock = this.putLock; // 竞争锁资源 putLock.lock(); try { // 再次做一个判断,查看是否还有空间 if (count.get() < capacity) { // enqueue,扔数据 enqueue(node); // 将数据个数 + 1 c = count.getAndIncrement(); // 拿到count的值 小于 长度限制 // 有生产者在基于await挂起,这里添加完数据后,发现还有空间可以存储数据, // 唤醒前面可能已经挂起的生产者 // 因为这里生产者和消费者不是互斥的,写操作进行的同时,可能也有消费者在消费数据。 if (c + 1 < capacity) // 唤醒生产者 notFull.signal(); } } finally { // 释放锁资源 putLock.unlock(); } // 如果c == 0,代表添加数据之前,队列元素个数是0个。 // 如果有消费者在队列没有数据的时候,来消费,此时消费者一定会挂起线程 if (c == 0) // 唤醒消费者 signalNotEmpty(); // 添加成功返回true,失败返回-1 return c >= 0; } //================================================ private void enqueue(Node<E> node) { // 将当前Node设置为last的next,并且再将当前Node作为last last = last.next = node; } //================================================ private void signalNotEmpty() { // 获取读锁 final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { // 唤醒。 notEmpty.signal(); } finally { takeLock.unlock(); } } sync -> wait / notify
put方法
public void put(E e) throws InterruptedException { 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; putLock.lockInterruptibly(); try { while (count.get() == capacity) { // 一直挂起线程,等待被唤醒 notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); }
消费者方法实现原理
public E poll() { // 拿到队列数据个数的计数器 final AtomicInteger count = this.count; // 当前队列中数据是否0 if (count.get() == 0) // 说明队列没数据,直接返回null即可 return null; // 声明返回结果 E x = null; // 标记 int c = -1; // 获取消费者的takeLock final ReentrantLock takeLock = this.takeLock; // 加锁 takeLock.lock(); try { // 基于DCL,确保当前队列中依然有元素 if (count.get() > 0) { // 从队列中移除数据 x = dequeue(); // 将之前的元素个数获取,并-- c = count.getAndDecrement(); if (c > 1) // 如果依然有数据,继续唤醒await的消费者。 notEmpty.signal(); } } finally { // 释放锁资源 takeLock.unlock(); } // 如果之前的元素个数为当前队列的限制长度, // 现在消费者消费了一个数据,多了一个空位可以添加 if (c == capacity) // 唤醒阻塞的生产者 signalNotFull(); return x; } //================================================ private E dequeue() { // 拿到队列的head位置数据 Node<E> h = head; // 拿到了head的next,因为这个是哨兵Node,需要拿到的head.next的数据 Node<E> first = h.next; // 将之前的哨兵Node.next置位null。help GC。 h.next = h; // 将first置位新的head head = first; // 拿到返回结果first节点的item数据,也就是之前head.next.item E x = first.item; // 将first数据置位null,作为新的head first.item = null; // 返回数据 return x; } //================================================ private void signalNotFull() { final ReentrantLock putLock = this.putLock; putLock.lock(); try { // 唤醒生产者。 notFull.signal(); } finally { putLock.unlock(); } }