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等,已经非常成熟,在大大小小的公司和项目中也已经广泛使用. 有些项目中,如果是只使用初步的 ...
随机推荐
- webgl自学笔记——深度监测与混合
这一章中关于webgl中颜色的使用我们将深入研究.我们将从研究颜色在webgl和essl中如何被组装和获取开始.然后我们讨论在物体.光照和场景中颜色的使用.这之后我们将看到当一个物体在另一个物体前面是 ...
- Nodejs+Express 搭建 web应用
简单的记录下关于如何使用nodejs+Express 极速搭建一个web应用. 项目所需,要用到nodejs,那就去学咯.简单的看了下 七天学会NodeJS,Node.js 教程.发现其实好简单的,分 ...
- RecyclerView和ListView比较
题记: RecyclerView说是目前最重要的控件也不为过,ListView虽然被RecyclerView光芒掩盖,但也仍有着自己的d地位:这个问题不是很偏重原理或实践,而更多的是,针对面试中会问到 ...
- 学习ASP.NET Core Razor 编程系列十一——把新字段更新到数据库
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- .Net Core Web Api 上传女朋友的照片到微软云Azure Storage
前言 实现一个Web Api,把女朋友照片保存到Azure云的storage里. Image Upload Api 在对应的Api Controller里,加上attribute: [Consumes ...
- 深入解读MySQL8.0 新特性 :Crash Safe DDL
前言 在MySQL8.0之前的版本中,由于架构的原因,mysql在server层使用统一的frm文件来存储表元数据信息,这个信息能够被不同的存储引擎识别.而实际上innodb本身也存储有元数据信息.这 ...
- SLAM+语音机器人DIY系列:(二)ROS入门——8.理解roslaunch在大型项目中的作用
摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...
- SLAM+语音机器人DIY系列:(四)差分底盘设计——3.底盘通信协议
摘要 运动底盘是移动机器人的重要组成部分,不像激光雷达.IMU.麦克风.音响.摄像头这些通用部件可以直接买到,很难买到通用的底盘.一方面是因为底盘的尺寸结构和参数是要与具体机器人匹配的:另一方面是因为 ...
- 2017年IT行业测试调查报告
在刚刚过去的2017年, 我们来一起看一下2017年IT行业测试调查报告 还是1到5名测试工程师最多 Test Architects 在北上广一线城市已经出现 https://www.lagou.co ...
- asp.net easyui 动态绑定下拉框
前台: <title>标题</title> <link href="EasyUi_v1.3.4/easyui/themes/default/easyui.css ...