阻塞队列源码浅析

概述
  • 阻塞队列的好处:当队列满时,不需要用代码失去实现禁止往队列中存储数据,当队列不为空时,也会禁止消费者来消费,给使用这提供了方便性与安全性。
  • 阻塞队列的种类:
    • ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
    • PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
    • DelayQueue:使用优先级队列实现的无界阻塞队列。
    • SynchronousQueue:不存储元素的阻塞队列。
    • LinkedTransferQueue:由链表结构组成的无界阻塞队列
    • LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
  • 几个方法:
    • 增加(只能在队尾增加元素):add、put、offer
    • 队首(只能在队头移除元素):peek、poll、take
    • 移除:remove(在队头移除元素),remove(object o)(移除队列中的某个元素)
源码
  • 本节主要介绍List类型的阻塞队列

  • 队列和集合->Array结构

    • 相同点:都是线性结构、底层实现Object数组
    • 区别:
      • 集合各个位置都可以添加和删除元素,队列只能在一端进行插入一端进行删除,是先进先出的线性结构,由于队列是先进先出,插入在队尾,移除在队列的头部。
      • 集合中移除元素有可能会导致一部分位置的元素index发生变化,而队列中在某一端移除和增加元素不会影响其他的位置的index,也就是说集合中移除元素有可能对数据进行移动。例如List[1,2,3],移除元素2后的结果List[1,3],元素3的index发生了变化。Deque[1,2,3],移除一个(只能从1的一段移除元素)Deque[null,2,3],元素中的index并没有发生变化。
      • 集合和队列移除某个元素的实现原理是大致相同的,remove(object),都需要将数组中元素进行移动。
  • 实现原理思路:存放元素的结构是Object数组,在队列的实现数组中,队列是一个循环的数组,插入数据或者移除数据时的索引和数组的长度的数值相同时,需要将索引重置为初始值。

    • 概述:一端插入一端删除,在有限的空间内,要知道下一次插入数据的位置和移除数据的位置,所以在队列的实现中需要记录插入元素的位置和移除元素的位置。队列还存在空和满的情况,所以还需要知道队列中存在元素的个数。
    • 增加实现:
      • 将当前元素插入队尾,队尾可以是数组当中的任何位置,
      • 从队列中获取元素的位置正好和插入位置相反,从队列中移除元素在队列的头部,增加元素在队列的尾部,所以队列是一个循环的结构,当数组满时,需要等待队列中的元素被消费,消费的元素只能是队列的头部,所以当队列满时需要将插入位置的索引置为队列的头部
    • 移除的实现:移除头索引的数据,如果队列为空,则等待,如果移除了数据,队列的数量减一
    • remove(Object):在队列中查找该元素,如果找到则移除该元素,同时将后面的元素移动,如果没有找到,则不删除。
  • 全局变量

final nal Object[] items;
int takeIndex;//记录队头元素的索引 头索引
int putIndex;// 记录队列尾部的索引 尾索引
int count;
final ReentrantLock lock;
 /** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts 主要用于控制生产者,当队列满时将生产者置于阻塞的状态,
*当队列不为空时,通知生产者生产产品,生产者处于(需要锁+CPU可以将生产的产品存入队列)
*/
private final Condition notFull;
transient Itrs itrs = null;  //迭代器,阻塞队列实现的迭代器,初始化时迭代器时,会将队列中的所有数据复制到迭代器中
  • take pull方法
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();//lock是可端锁,当线程A请求take方法没有获得锁时,此时线程A处于阻塞的状态,可以被中断
        try {
            while (count == 0)//队列中没有元素,将消费者线程置于阻塞
                notEmpty.await();
            return dequeue(); //获取元素
        } finally {
            lock.unlock();
        }
}

public E poll() {//remove()调用该方法-> remove()和remove(Object o不同)
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
         return (count == 0) ? null : dequeue();
     } finally {
         lock.unlock();
     }
 }

private E dequeue() {
        final Object[] items = this.items
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];//当前要移除的元素
        items[takeIndex] = null;//将队列中该位置的元素置为null
        if (++takeIndex == items.length)//因为队列是循环结构,当头索引在队列的最后一个位置时,需要将头索引指向数组的第一个位置
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();//唤醒处于阻塞中的生产者线程
        return x;
}

  • offer put

public boolean offer(E e) {//add调用该方法
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}
public void put(E e) throws InterruptedException {
       checkNotNull(e);
       final ReentrantLock lock = this.lock;
       lock.lockInterruptibly();
       try {
           while (count == items.length)//当队列满时,使生产者处于阻塞状态
               notFull.await();
           enqueue(e);
       } finally {
           lock.unlock();
       }
}

private void enqueue(E x) {
      // assert lock.getHoldCount() == 1;
      // assert items[putIndex] == null;
      final Object[] items = this.items;
      items[putIndex] = x;//增加元素
      if (++putIndex == items.length)//尾索引处于数组的最后一个位置时,需要将尾索引置为第一个位置
          putIndex = 0;
      count++;
      notEmpty.signal();//唤醒消费者线程
}

  • remove(object)
public boolean remove(Object o) {
        if (o == null) return false;
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count > 0) {//数组中存在元素
                final int putIndex = this.putIndex;/
                int i = takeIndex;
                //从头索引开始到尾索引查找是否存在要移除的元素,如果找到,移除该位置的元素
                do {
                    if (o.equals(items[i])) { //队列中的该元素和移除的目标元素相同,
                        removeAt(i);//移除该位置的元素
                        return true;
                    }
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();
        }
}

void removeAt(final int removeIndex) {

       final Object[] items = this.items;
       if (removeIndex == takeIndex) { //移除的元素正好的队尾元素
           // removing front item; just advance
           items[takeIndex] = null;
           if (++takeIndex == items.length)
               takeIndex = 0;
           count--;
           if (itrs != null)
               itrs.elementDequeued();
       } else {
           //循环查找索引的位置,同时移动元素,存在数据的范围是takeIndex到putIndex
           final int putIndex = this.putIndex;
           for (int i = removeIndex;;) {
               int next = i + 1;
               if (next == items.length)//index到数组的最后一个位置
                   next = 0;
               if (next != putIndex) {//移动元素
                   items[i] = items[next];
                   i = next;
               } else {//找到元素删除
                   items[i] = null;
                   this.putIndex = i;
                   break;
               }
           }
           count--;
           if (itrs != null)
               itrs.removedAt(removeIndex);
       }
       notFull.signal();
   }

问题
  • 阻塞队列为什么要加锁实现增加和移除操作?
    • 会出现线程安全问题,同一个位置同时存在多个生产者生产产品时会出现替换操作,同一个位置多个消费时会出现重复消费的问题
  • 通过ArrayDeque和wait、notify、notifyAll自定义实现的阻塞队列时,如果存在多个生产者(p1,p2,p3)和多个消费者(c1,c2,c3),加锁对象是该ArrayDeque队列,某个时刻p1生产了产品后,当前队列满了,此时调用了notify方法会唤醒那个线程?如果唤醒生产者,则生产者消费产品,如果唤醒消费者呢?如果消费者数量较多,notify会不会唤醒很多消费者而没有唤醒一个消费者呢?notify 并没有指定应该唤醒那种线程。
    • notify唤醒的线程是处于阻塞中的所有线程,而不是指哪一个,也不能指定哪一个。
  • 阻塞队列实现了迭代器,在移除元素时对迭代器中的元素进行操作,打算找个时间把List和ArrayBlockingQueue的迭代器实现总结一下。if (itrs != null) itrs.removedAt(removeIndex); 这一段就是对迭代器中的元素进行操作。
使用
public class Test {
    private int queueSize = 10;
    private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(queueSize);
     
    public static void main(String[] args)  {
        Test test = new Test();
        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();
         
        producer.start();
        consumer.start();
    }
     
    class Consumer extends Thread{
         
        @Override
        public void run() {
            consume();
        }
         
        private void consume() {
            while(true){
                try {
                    queue.take();
                    System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
     
    class Producer extends Thread{
         
        @Override
        public void run() {
            produce();
        }
         
        private void produce() {
            while(true){
                try {
                    queue.put(1);
                    System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
候补

wait、notify、await、signal 的思考:

  • wait,await是使获得锁的线程处于阻塞状态,同时释放锁,释放cpu资源,之后的线程需要被唤醒才能进行使用。
  • notify,signal是唤醒等待线程中的一个线程,某一个线程,其实所有的等待线程形成了要给队列,所以唤醒线程从队列中获取的,也就是有序的,公平锁和非公平锁主要是在形成队列时候的区别
  • 仔细观察下阻塞队列存在两个锁,队列一端进,一端出,所以分别在进出的时候有两个锁,这两个锁的目的就是为了在唤醒线程时候有所指向,如果进队列时发现线程满了,无法进入,那么唤醒的出队列的线程对队列中的元素进行消费,反之出队列通知入队列生产元素~
参考

https://www.cnblogs.com/dolphin0520/p/3932906.html
https://blog.csdn.net/u013991521/article/details/53068713

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值