本文聊一下 JUC 下的 LinkedBlockingQueue 队列,先说说 LinkedBlockingQueue 队列的特点,然后再从源码的角度聊一聊 LinkedBlockingQueue 的主要实现~

LinkedBlockingQueue 有以下特点:

  • LinkedBlockingQueue 是阻塞队列,底层是单链表实现的~
  • 元素从队列尾进队,从队列头出队,符合FIFO~
  • 可以使用 Collection 和 Iterator 两个接口的所有操作,因为实现了两者的接口~
  • LinkedBlockingQueue 队列读写操作都加了锁,但是读写用的是两把不同的锁,所以可以同时读写操作~

LinkedBlockingQueue 队列继承了 AbstractQueue 类,实现了 BlockingQueue 接口,LinkedBlockingQueue 主要有以下接口:

  1. //将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量)
  2. //在成功时返回 true,如果此队列已满,则抛IllegalStateException。
  3. boolean add(E e);
  4. //将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量)
  5. // 将指定的元素插入此队列的尾部,如果该队列已满,
  6. //则在到达指定的等待时间之前等待可用的空间,该方法可中断
  7. boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
  8. //将指定的元素插入此队列的尾部,如果该队列已满,则一直等到(阻塞)。
  9. void put(E e) throws InterruptedException;
  10. //获取并移除此队列的头部,如果没有元素则等待(阻塞),
  11. //直到有元素将唤醒等待线程执行该操作
  12. E take() throws InterruptedException;
  13. //获取并移除此队列的头,如果此队列为空,则返回 null。
  14. E poll();
  15. //获取并移除此队列的头部,在指定的等待时间前一直等到获取元素, //超过时间方法将结束
  16. E poll(long timeout, TimeUnit unit) throws InterruptedException;
  17. //从此队列中移除指定元素的单个实例(如果存在)。
  18. boolean remove(Object o);
  19. //获取但不移除此队列的头元素,没有则跑异常NoSuchElementException
  20. E element();
  21. //获取但不移除此队列的头;如果此队列为空,则返回 null。
  22. E peek();

LinkedBlockingQueue 队列的读写方法非常的多,但是常用的是 put()take()方法,因为它们两是阻塞的,所以我们就从源码的角度来聊一聊 LinkedBlockingQueue 队列中这两个方法的实现。

先来看看 put()方法,源码如下:

  1. public void put(E e) throws InterruptedException {
  2. if (e == null) throw new NullPointerException();
  3. // 预先设置 c 的值为 -1,表示失败
  4. int c = -1;
  5. Node<E> node = new Node<E>(e);
  6. // 获取写锁
  7. final ReentrantLock putLock = this.putLock;
  8. // 获取当前队列的大小
  9. final AtomicInteger count = this.count;
  10. // 设置可中断锁
  11. putLock.lockInterruptibly();
  12. try {
  13. // 队列满了
  14. // 当前线程阻塞,等待其他线程的唤醒(其他线程 take 成功后就会唤醒此处线程)
  15. while (count.get() == capacity) {
  16. // 无限期等待
  17. notFull.await();
  18. }
  19. // 新增到队列尾部
  20. enqueue(node);
  21. // 获取当前的队列数
  22. c = count.getAndIncrement();
  23. // 如果队列未满,尝试唤醒一个put的等待线程
  24. if (c + 1 < capacity)
  25. notFull.signal();
  26. } finally {
  27. // 释放锁
  28. putLock.unlock();
  29. }
  30. if (c == 0)
  31. signalNotEmpty();
  32. }

put()方法的源码并不难,非常容易就看懂,put()方法的过程大概如下:

  • 1、先加锁,保证容器的并发安全~
  • 2、队列新增数据,将数据追加到队列尾部~
  • 3、新增时,如果队列满了,当前线程是会被阻塞的,等待被唤醒~
  • 4、新增数据成功后,在适当时机,会唤起 put 的等待线程(队列不满时),或者 take 的等待线程(队列不为空时),这样保证队列一旦满足 put 或者 take 条件时,立马就能唤起阻塞线程,继续运行,保证了唤起的时机不被浪费offer 就有两两种,一种是直接返回 false,另一种是超过一定时间后返回 false~
  • 5、释放锁~

其他的新增方法,例如 offer,可以查看源码,跟put() 方法大同小异,相差不大~

再来看看 take()方法,源码如下:

  1. public E take() throws InterruptedException {
  2. E x;
  3. // 默认负数
  4. int c = -1;
  5. // 当前链表的个数
  6. final AtomicInteger count = this.count;
  7. //获取读锁
  8. final ReentrantLock takeLock = this.takeLock;
  9. takeLock.lockInterruptibly();
  10. try {
  11. // 当队列为空时,阻塞,等待其他线程唤醒
  12. while (count.get() == 0) {
  13. notEmpty.await();
  14. }
  15. // 从队列的头部拿出一个元素
  16. x = dequeue();
  17. //减一操作,C比真实的队列数据大一
  18. c = count.getAndDecrement();
  19. // c 大于 0 ,表示队列有值,可以唤醒之前被阻塞的读线程
  20. if (c > 1)
  21. notEmpty.signal();
  22. } finally {
  23. // 释放锁
  24. takeLock.unlock();
  25. }
  26. // 队列未满,可以唤醒 put 等待线程~
  27. if (c == capacity)
  28. signalNotFull();
  29. return x;
  30. }

take()方法跟 put() 方法类似,是一个相反的操作,我就不做过多的说明了~

以上就是 LinkedBlockingQueue 队列的简单源码解析,希望对你的面试或者工作有所帮助,感谢你的阅读~

欢迎关注公众号【互联网平头哥】,一起成长,一起进步~。

互联网平头哥

版权声明:本文为jamaler原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/jamaler/p/12849927.html