BAT面试题:使用数组实现一个简单的阻塞队列
这道题是我亲身经历的一道大厂面试题,非常值得分享!
这道题可以分为两个步骤进行编码解答,第一步是基于数组实现一个队列,第二步是实现线程阻塞。
如果是基于数组实现栈的数据结构,那么我们只需要一个指针进行来回移动即可。
想象一下,脑海中有一个竖立起来的栈,指针上移代表元素进栈,指针下移,代表元素出栈,整个过程只需要一个指针进行上下移动即可。
由此可以写出下面的代码:
import java.util.Arrays;
import java.util.EmptyStackException;
public class ArrayStack<T> {
private Object[] elements = new Object[16]; //数组大小默认16
private int count; //1.-1后指向栈内末尾的元素 2.统计栈内元素的数量
public void push(T e){
//数组扩容
if (count == elements.length){
elements = Arrays.copyOf(elements,2*count+1);
}
elements[count++] = e;
}
public T pop(){
if (count == 0){
throw new EmptyStackException();
}
T o = (T) elements[--count];
elements[count] = null; //防止内存泄漏
return o;
}
public static void main(String[] args) {
ArrayStack<Integer> arrayStack = new ArrayStack<>();
arrayStack.push(1);
arrayStack.push(2);
System.out.println(arrayStack.pop()); //2
System.out.println(arrayStack.pop()); //1
}
}
但是,基于数组实现队列却需要两个指针进行来回移动。
想象一下,脑海中有一个横放的空队列,在向队列进行ADD操作时,ADD指针从首端右移,直到移到末端填满队列;在向一个满队列进行GET操作时,GET指针从首端右移,直到移到末端取出所有元素。
这些步骤都是需要前提条件的,满队列无法进行ADD操作,同理,空队列无法进行GET操作,在ADD和GET操作之前还需要对此进行检查。
其次,ADD和GET指针会一直循环移动下去,它们移动到末端并不代表任何意义(即ADD指针移动到末端不代表队列已满,GET指针移动到末端不代表队列已空),所以,我们需要一个变量用做计数器,专门负责统计队列元素数量,检查队列是否已满或已空。
至于阻塞/唤醒部分的逻辑就比较简单了,只需要使GET线程访问空队列时进行阻塞,ADD线程访问满队列时进行阻塞即可,并在GET方法、ADD方法操作结束时唤醒一下对方线程,如果对方正在阻塞状态就可以被唤醒继续向下运行。
由此可以写出下面的代码:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ArrayBlockingQueue<T> {
private Lock lock = new ReentrantLock();
private Object[] item;
//两个指针负责ADD与GET操作
//count负责统计元素数量
private int addIndex, getIndex, count;
//等待、通知
private Condition addCondition = lock.newCondition();
private Condition getCondition = lock.newCondition();
public ArrayBlockingQueue(int size) {
item = new Object[size];
}
public void add(T t) {
lock.lock();
try {
System.out.println("正在ADD对象:" + t);
while (count == item.length) {
try {
System.out.println("队列已满,阻塞ADD线程");
addCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//队列未满,添加对象并使计数器+1
item[addIndex++] = t;
count++;
//ADD指针指向末端,重置
if (addIndex == item.length) {
addIndex = 0;
}
System.out.println("唤醒GET线程");
getCondition.signal();
} finally {
lock.unlock();
}
}
public T get() {
lock.lock();
try {
while (count == 0) {
try {
System.out.println("队列空了,阻塞GET线程");
getCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//队列不空,获取对象并使计数器-1
T t = (T) item[getIndex++];
System.out.println("正在GET对象:" + t);
count--;
//GET指针到达末端、重置
if (getIndex == item.length) {
getIndex = 0;
}
System.out.println("唤醒ADD线程");
addCondition.signal();
return t;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
queue.add(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
queue.get();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
// 打印输出:
// 正在ADD对象:0
// 唤醒GET线程
// 正在GET对象:0
// 唤醒ADD线程
// 队列空了,阻塞GET线程
// 正在ADD对象:1
// 唤醒GET线程
// 正在GET对象:1
// 唤醒ADD线程
// 队列空了,阻塞GET线程
// 正在ADD对象:2
// 唤醒GET线程
// 正在GET对象:2
// 唤醒ADD线程
BAT面试题:使用数组实现一个简单的阻塞队列的更多相关文章
- 自己动手系列----使用数组实现一个简单的Map
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同.Java 语言中提供的数组是用来存储固定大小的同类型元素. 这里提一下,数组的优缺点: 优点: 1. 使用索 ...
- Java编程的逻辑 (61) - 内存映射文件及其应用 - 实现一个简单的消息队列
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java实现一个简单的循环队列
在某些时候,我们不能被要求像数组一样可以使用索引随机访问,而是需要被限制顺序处理业务,今天介绍一种先进先出(FIFO)的线性数据结构:队列, 当然,还有后进先出(LIFO)的处理方式,即为栈(后续有时 ...
- 面试之路(8)-BAT面试题之数组和链表的区别
两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用 各自的特点: 数组: 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素.但是如果要在数组中增加一个 ...
- 自己动手系列----使用数组实现一个简单的Set
Set:注重独一无二的性质,该体系集合可以知道某物是否已近存在于集合中,不会存储重复的元素用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复.主要有HashSet和TreeSet两大实现类. ...
- 演示一个简单的Redis队列
0.Windows Service版下载 https://github.com/rgl/redis/downloads 1.新建一个Console项目 打开Nuget控制台,执行以下命令 Instal ...
- 一个简单的js队列,逻辑很清晰
function Queue(type) { //type 是否是一个接着一个执行 function QueueConst() {} QueueConst.execute_ing=[], QueueC ...
- 教你如何使用Java手写一个基于链表的队列
在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...
- nodejs一个函数实现消息队列中间件
消息队列中间件(Message Queue)相信大家不会陌生,如Kafka.RabbitMQ.RocketMQ等,已经非常成熟,在大大小小的公司和项目中也已经广泛使用. 有些项目中,如果是只使用初步的 ...
随机推荐
- 2019腾讯WXG移动客户端暑期实习面经
微信这个比较迷,二面完官网流程灰了,但是过了一周多突然来三面,下午面完三面晚上HR面,第三天offer call, 莫名其妙过了 之前以为已经挂了,所以没有写面经,现在距一面已经快一个月了,只能还记得 ...
- 跟我一起学opencv 第二课之图像的掩膜操作
1.掩膜(mask)概念 用选定的图像,图形或物体,对处理的图像(全部或局部)进行遮挡,来控制图像处理的区域或处理过程.用于覆盖的特定图像或物体称为掩模或模板.光学图像处理中,掩模可以足胶片,滤光片等 ...
- python --- 冒泡排序算法
别想太多了,这个冒泡排序就是我们脑海中想到的那个冒泡,就好像是气泡一样,较小的元素比较轻,从而要往上浮出来, 冒泡排序算法. 要对‘气泡’序列处理若干遍.所谓一遍处理,就是自底向上检查一遍这个序列,并 ...
- 小乌龟git
一.概念 分布式版本控制系统.诞生于Linux社区,有兴趣可以去了解下git的前世今生. 与集中式版本控制有明显区别.集中式:集中管理的服务器,保存所有的修订版本,协同工作的人通过客户端连接服务器,取 ...
- ASP.NET Core中使用GraphQL - 第九章 在GraphQL中处理多对多关系
ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...
- DotNetCore跨平台~2.0提前发布喽
回到目录 提前1个多月把2.0发布出来了,小微真的把持不住了,哈哈! windows上安装 http://mp.weixin.qq.com/s/ueJdhaBBCHga0sQlVD6YiQ https ...
- [macOS开发.NET Core] 一个简单的WEB程序
上一篇咱们提到了在macOS下选进行开发. 咱们已经把工具准备完成了. 现在咱们做一个简单的DEMO 创建WEB程序  之前咱们已经创建过WEB程序,并且成功的运行过数据 现在咱们创建一个页面,并显 ...
- Linux framebuffer deferred io机制
一.总体框架 deferred io机制主要用于驱动没有实现自刷新同时应用层又不想调用FBIOPAN_DISPLAY的一个折中方案, 使用ioctrl FBIOPAN_DISPLAY好处是节能, 驱 ...
- Vue基础(环境配置、内部指令、全局API、选项、内置组件)
1.环境配置 安装VsCode 安装包管理工具:直接下载 NodeJS 进行安装即可,NodeJS自带 Npm 包管理工具,下载地址:https://nodejs.org/en/download/安装 ...
- 微信公众号开发C#系列-5、用户和用户组管理-支持同步
1.概述 眼前时下流行的经济有个叫粉丝经济,粉丝带动收益.一个好运营良好的公众号肯定会有一大批的粉丝团,如何挖掘粉丝来产生效益,是微信营销的关键.微信公众号后台本身提供了粉丝(用户)与用户分组的管理, ...