2)出队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 {// 如果队列无元素,则阻塞在notEmpty条件上(消费者线程阻塞)while (count.get() == 0) {notEmpty.await();}// 否则,出队x = dequeue();c = count.getAndDecrement();//长度-1,返回原值if (c > 1)// 如果取之前队列长度大于1,notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程,原因与入队同理notEmpty.signal();} finally {takeLock.unlock(); // 真正唤醒消费者线程}// 为什么队列是满的才唤醒阻塞在notFull上的线程呢?// 因为唤醒是需要加putLock的,这是为了减少锁的次数,所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程,// 这也是锁分离带来的代价// 如果取之前队列长度等于容量(已满),则唤醒阻塞在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; // 方便GChead = first;E x = first.item;first.item = null;return x;}private void signalNotFull() {final ReentrantLock putLock = this.putLock;putLock.lock();try {notFull.signal();// notFull条件队列转同步队列,准备唤醒阻塞在notFull上的线程} finally {putLock.unlock(); // 解锁,这才会真正的唤醒生产者线程}}LinkedBlockingQueue总结【1】无界阻塞队列,可以指定容量,默认为 Integer.MAX_VALUE,先进先出,存取互不干扰
【2】数据结构:链表(可以指定容量,默认为 Integer.MAX_VALUE,内部类Node存储元素)
【3】锁分离:存取互不干扰,存取操作的是不同的Node对象(takeLock【取Node节点保证前驱后继不乱】,putLock【存Node节点保证前驱后继不乱】,删除时则两个锁一起加)【这是最大的亮点】
【4】阻塞对象(notEmpty【出队:队列count=0,无元素可取时,阻塞在该对象上】,notFull【入队:队列count=capacity,放不进元素时,阻塞在该对象上】)
【5】入队,从队尾入队,由last指针记录 。
【6】出队,从队首出队,由head指针记录 。
【7】线程池中采用LinkedBlockingQueue而不采用ArrayBlockingQueue的原因便是因为锁分离带来了性能的提升,大大提高队列的吞吐量 。
【LinkedBlockingQueue详解】
经验总结扩展阅读
- 详解ROMA Connect API 流控实现技术
- Go_Goroutine详解
- Go_Channel详解
- Docker | 容器数据卷详解
- MyBatis之ResultMap的association和collection标签详解
- Future详解
- Go的网络编程详解
- gorm中的关联操作详解
- Go中的闭包、递归
- liunx之expect操作详解