Java 经典面试题:聊一聊 JUC 下的 LinkedBlockingQueue
Java 经典面试题:聊一聊 JUC 下的 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 队列的简单源码解析,希望对你的面试或者工作有所帮助,感谢你的阅读~
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
JVM系列-2、JVM内存结构
JVM系列-2、JVM内存结构 一、JVM内存结构 1.1、栈(JVM Stacks)存放局部变量(定义在方法中的变量和定义在方法参数列表上的变量)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)、方法的执行。栈里面的变量不会附初始值。是线程私有的,它的生命周期与线程相同,线程之间资源不共享。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈是一块连续的空间,相比堆来说较小,运行速度快,不需要垃圾回收机制。在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈)...
- 下一篇
Signal 协议 的 java接口库
Signal 协议 的 java接口库 概述Signal协议是一种基于双棘轮技术的前向保密协议。可应用于在同步和异步这两种消息传递环境。 接口库开源地址:https://github.com/signalapp/libsignal-protocol-java 预共享密钥Signal协议中应用了预共享密钥(PreKey)概念。预共享密钥包括一个公钥和一个对应的惟一ID。 准备数据时,客户端生成一个已签名的预共享密钥,以及一组未签名的预共享密钥的列表,并将它们全部传输到服务器上存储,以供查询。预共享密钥也可以签名。 会话Signal协议是面向会话的。客户端建立一个“会话”,用于后续的加密/解密操作。一旦建立了一个会话,通信过程中就不必再拆掉它。 会话可以通过以下三种方式建立: 基于预共享密钥包(PreKeyBundles):预向某收件人发送消息的客户端,可以通过从服务器上检索该收件人的预共享密钥包来建立会话。预共享密钥消息(PreKeySignalMessages):客户端可以从收件人接收预共享密钥消息并使用它建立会话。密钥交换消息(KeyExchangeMessages):两个客户端可以...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- Hadoop3单机部署,实现最简伪集群
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2整合Redis,开启缓存,提高访问速度