深入理解Java并发编程之LinkedBlockingQueue队列

作者:派大大大星 时间:2022-10-02 06:35:22 

前面一篇文章我们介绍了使用CAS算法实现的非阻塞队列ConcurrentLinedQueue, 下面我们来介绍使用独占锁实现的阻塞队列LinkedBlockingQueue。

LinkedBlockingQueue也是使用单向链表实现的,其也有两个Node,分别用来存放首、尾节点,并且还有一个初始值为0的原子变量count,用来记录队列元素个数。另外还有两个ReentrantLock的实例,分别用来控制元素入队和出队的原子性,其中takeLock用来控制同时只有一个线程可以从队列头获取元素,其他线程必须等待,putLock控制同时只能有一个线程可以获取锁,在队列尾部添加元素,其他线程必须等待。另外,notEmpty 和 notFull 是条件变量,它们内部都有一个条件队列用来存放进队和出队时被阻塞的线程,其实这是生产者-消费者模型。如下是独占锁的创建代码。

private final AtomicInteger count = new AtomicInteger();

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
  • 当调用线程在LinkedBlockingQueue 实例上执行take、poll 等操作时需要获取到 takeLock 锁,从而保证同时只有一个线程可以操作链表头节点。另外由于条件变量 notEmpty 内部的条件队列的维护使用的是takeLock的锁状态管理机制,所以在调用notEmpty的await 和signal方法前调用线程必须先获取到 takeLock锁,否则会抛出IllegalMonitorStateException 异常。notEmpty内部则维护着一个条件队列,当线程获取到takeLock 锁后调用 notEmpty的await 方法时,调用线程会被阻塞,然后该线程会被放到notEmpty内部的条件队列进行等待,直到有线程调用了notEmpty的 signal 方法。

  • 在LinkedBlockingQueue实例上执行put、offer等操作时需要获取到putLock锁,从而保证同时只有一个线程可以操作链表尾节点。同样由于条件变量 notFull 内部的条件队列的维护使用的是putLock的锁状态管理机制,所以在调用 notFull 的 await 和 signal 方法前调用线程必须先获取到putLock锁,否则会抛出 IllegalMonitorStateException 异常。notFull 内部则维护着一个条件队列,当线程获取到 putLock 锁后调用notFull的await 方法时,调用线程会被阻塞,然后该线程会被放到notFull 内部的条件队列进行等待,直到有线程调用了 notFull 的 signal 方法。如下是LinkedBlockingQueue 的无参构造函数的代码。

如下是LinkedBlockingQueue的无参构造代码

public static final int MAX_VALUE = 0x7fffffff;
public LinkedBlockingQueue() {
   this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
   if (capacity <= 0) throw new IllegalAgrumentException();
   this.capacity = capacity;
   last = head = new Node<E>(null);
}

由该代码可知,默认队列容量为0x7fffffff,用户也可以自己指定容量,所以从一定程度上可以说LinkedBlockingQueue是有界阻塞队列。

offer操作

public boolean offer(E e) {
//(1)
   if (e == null) throw new NullPointerException();
   //(2)
   final AtomicInteger count = this.count;
   if (count.get() == capacity)
       return false;
       //(3)
   int c = -1;
   Node<E> node = new Node<E>(e);
   final ReentrantLock putLock = this.putLock;
   putLock.lock();
   try {
   //(4)
       if (count.get() < capacity) {
           enqueue(node);
           c = count.getAndIncrement();
           //(5)
           if (c + 1 < capacity)
               notFull.signal();
       }
   } finally {
   //(6)
       putLock.unlock();
   }
   //(7)
   if (c == 0)
       signalNotEmpty();
       //(8)
   return c >= 0;
}

代码(2)判断如果当前队列已满则丢弃当前元素并返回false

代码(3)获取到 putLock 锁,当前线程获取到该锁后,则其他调用put和 offer操的线程将会被阻塞(阻塞的线程被放到putLock锁的AQS阻塞队列)。

代码(4)这里重新判断当前队列是否满,这是因为在执行代码(2)和获取到 putLock 锁期间可能其他线程通过 put 或者offer 操作向队列里面添加了新元素。重新判斯队列确实不满则新元素入队,并递增计数器。

代码(5)判断如果新元素入队后队列还有空闲空间,则唤醒notFull的条件队列里面因为调用了notFull的await操作(比如执行put方法而队列满了的时候)而被阻塞的一个线程,因为队列现在有空闲所以这里可以提前唤醒一个入队线程。

代码(6)则释放获取的putLock 锁,这里要注意,锁的释放一定要在finally里面做因为即使try块抛出异常了,finally也是会被执行到。另外释放锁后其他因为调用put 操作而被阻塞的线程将会有一个获取到该锁。

代码(7)中的c0说明在执行代码(6)释放锁时队列里面至少有一个元素,队列里面有元素则执行signalNotEmpty操作.

来源:https://juejin.cn/post/7086457613394640926

标签:Java,LinkedBlockingQueue,队列
0
投稿

猜你喜欢

  • Android实现网易云音乐高仿版流程

    2023-05-13 22:08:50
  • 微信公众号支付(二)实现统一下单接口

    2023-01-12 21:29:18
  • C#实现根据指定容器和控件名字获得控件的方法

    2023-09-26 12:23:03
  • Android使用IntentService进行apk更新示例代码

    2022-01-19 12:12:07
  • C#实现winform渐变效果的方法

    2023-03-14 00:26:06
  • java FastJson的简单用法

    2022-07-07 23:51:47
  • SpringBoot登录用户权限拦截器

    2022-07-15 04:18:04
  • 在C#程序中对MessageBox进行定位的方法

    2022-03-04 18:45:26
  • C#泛型与非泛型性能比较的实例

    2022-01-31 17:26:51
  • Android App开发中HTTP扩展包OkHttp的入门使用指南

    2023-10-01 11:11:28
  • Java实现多任务执行助手

    2023-05-17 14:15:30
  • 万能RecyclerView分割线

    2023-04-19 16:10:04
  • C# 使用multipart form-data方式post数据到服务器

    2023-12-20 21:10:58
  • java实现一次性压缩多个文件到zip中的方法示例

    2021-11-16 07:24:47
  • C#客户端程序Visual Studio远程调试的方法详解

    2022-10-20 19:13:54
  • SpringBoot中配置SSL的同时支持http和https访问

    2022-06-30 00:58:58
  • 详解C#的排列组合

    2021-06-05 15:40:11
  • 简介Java的Hibernate框架中的Session和持久化类

    2023-04-17 14:41:13
  • Android自定义View之圆形进度条式按钮

    2023-09-07 22:51:28
  • c# Selenium爬取数据时防止webdriver封爬虫的方法

    2023-06-24 07:50:51
  • asp之家 软件编程 m.aspxhome.com