Java 高频面试题:聊一聊 JUC 下的 LinkedBlockingQueue

栏目: IT技术 · 发布时间: 4年前

内容简介:本文聊一下 JUC 下的 LinkedBlockingQueue 队列,先说说 LinkedBlockingQueue 队列的特点,然后再从源码的角度聊一聊 LinkedBlockingQueue 的主要实现~LinkedBlockingQueue 有以下特点:LinkedBlockingQueue 队列继承了

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

LinkedBlockingQueue 有以下特点:

  • 「LinkedBlockingQueue 是阻塞队列,底层是单链表实现的」~

  • 「元素从队列尾进队,从队列头出队,符合FIFO」~

  • 「可以使用 Collection 和 Iterator 两个接口的所有操作,因为实现了两者的接口」~

  • 「LinkedBlockingQueue 队列读写操作都加了锁,但是读写用的是两把不同的锁,所以可以同时读写操作」~

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

//将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量)
//在成功时返回 true,如果此队列已满,则抛IllegalStateException。 
boolean add(E e); 

//将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量) 
// 将指定的元素插入此队列的尾部,如果该队列已满, 
//则在到达指定的等待时间之前等待可用的空间,该方法可中断 
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; 

//将指定的元素插入此队列的尾部,如果该队列已满,则一直等到(阻塞)。 
void put(E e) throws InterruptedException; 

//获取并移除此队列的头部,如果没有元素则等待(阻塞), 
//直到有元素将唤醒等待线程执行该操作 
E take() throws InterruptedException; 

//获取并移除此队列的头,如果此队列为空,则返回 null。 
E poll();
//获取并移除此队列的头部,在指定的等待时间前一直等到获取元素, //超过时间方法将结束
E poll(long timeout, TimeUnit unit) throws InterruptedException; 

//从此队列中移除指定元素的单个实例(如果存在)。 
boolean remove(Object o); 

//获取但不移除此队列的头元素,没有则跑异常NoSuchElementException 
E element(); 

//获取但不移除此队列的头;如果此队列为空,则返回 null。 
E peek(); 

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

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

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // 预先设置 c 的值为 -1,表示失败
    int c = -1;
    Node<E> node = new Node<E>(e);
    // 获取写锁
    final ReentrantLock putLock = this.putLock;
    // 获取当前队列的大小
    final AtomicInteger count = this.count;
    // 设置可中断锁
    putLock.lockInterruptibly();
    try {
        // 队列满了
        // 当前线程阻塞,等待其他线程的唤醒(其他线程 take 成功后就会唤醒此处线程)
        while (count.get() == capacity) {
            // 无限期等待
            notFull.await();
        }
        // 新增到队列尾部
        enqueue(node);
        // 获取当前的队列数
        c = count.getAndIncrement();
        // 如果队列未满,尝试唤醒一个put的等待线程
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        // 释放锁
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

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

  • 1、先加锁,保证容器的并发安全~

  • 2、队列新增数据,将数据追加到队列尾部~

  • 3、新增时,如果队列满了,当前线程是会被阻塞的,等待被唤醒~

  • 4、新增数据成功后,在适当时机,会唤起 put 的等待线程(队列不满时),或者 take 的等待线程(队列不为空时),这样保证队列一旦满足 put 或者 take 条件时,立马就能唤起阻塞线程,继续运行,保证了唤起的时机不被浪费offer 就有两两种,一种是直接返回 false,另一种是超过一定时间后返回 false~

  • 5、释放锁~

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

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

public E take() throws InterruptedException {
    E x;
    // 默认负数
    int c = -1;
    // 当前链表的个数
    final AtomicInteger count = this.count;
    //获取读锁
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        // 当队列为空时,阻塞,等待其他线程唤醒 
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 从队列的头部拿出一个元素
        x = dequeue();
        //减一操作,C比真实的队列数据大一
        c = count.getAndDecrement();
        // c 大于 0 ,表示队列有值,可以唤醒之前被阻塞的读线程
        if (c > 1)
            notEmpty.signal();
    } finally {
        // 释放锁
        takeLock.unlock();
    }
    // 队列未满,可以唤醒 put 等待线程~
    if (c == capacity)
        signalNotFull();
    return x;
}

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

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

产品的视角:从热闹到门道

产品的视角:从热闹到门道

后显慧 / 机械工业出版社 / 2016-1-1 / 69.00

本书在创造性的提出互联网产品定义的基础上,为读者提供了一个从0基础到产品操盘手的产品思维培养方法! 全书以互联网产品定义为基础,提出了产品思维学习的RAC模型,通过认识产品、还原产品和创造产品三个阶段去培养产品思维和产品认知。 通过大量的图片和视觉引导的方法,作者像零基础的用户深入浅出的描绘了一条产品经理的自我修养路径,并且提供了知识地图(knowledge map)和阅读雷达等工具,......一起来看看 《产品的视角:从热闹到门道》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具