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等,已经非常成熟,在大大小小的公司和项目中也已经广泛使用. 有些项目中,如果是只使用初步的 ...
随机推荐
- Gradle的一些技巧和遇到的问题
全局变量的使用 在多个module的情况下,不同module的build.gradle文件中有部分配置项类似,或者依赖的类库,有部分是相同的,在维护上不是很方便,这个时候就可以考虑统一配置.在项目根目 ...
- Java原子类操作原理剖析
◆CAS的概念◆ 对于并发控制来说,使用锁是一种悲观的策略.它总是假设每次请求都会产生冲突,如果多个线程请求同一个资源,则使用锁宁可牺牲性能也要保证线程安全.而无锁则是比较乐观的看待这个问题,它会假设 ...
- 开放windows服务器端口-----以打开端口8080为例
[转载]原文地址:https://blog.csdn.net/spt_dream/article/details/75014619 本文记录两个内容: 1.win7下打开端口 2.服务器(2003或者 ...
- 【深度学习篇】--Windows 64下tensorflow-gpu安装到应用
一.前述 一直以为自己的笔记本不支持tensflow-gpu的运行,结果每次运行模型都要好久.偶然间一个想法,想试试自己的笔记本,结果竟然神奇的发现能用GPU.于是分享一下安装步骤. 二.具体 因为版 ...
- Python基础(reduce,filter,map函数)
map函数: map函数特点:对可迭代对象中的每个元素进行相同的操作(例如每个元素+1等等) #————————————————map函数———————————————————— #对列表的各个元素实 ...
- SVN问题解决--Attempted to lock an already-locked dir
今天上午更新uap(uap就是基于eclipse开发的软件,可以当eclipse来使用)上的代码时,发现在svn上更新不了,一直报这个Attempted to lock an already-lock ...
- 秋招提前批小结(CVTE一面挂、阿里三面挂)
7月27日:CVTE一面 30分钟(挂) 1.自我介绍 2.有没有做过JavaWeb相关的项目?你觉得难点在哪里呢? 3.你这个博客系统有没有加权限系统?如果被拦截封包获取了账号密码怎么办?(没加,凉 ...
- 拓扑排序的 +Leapms 线性规划模型
知识点 拓扑排序 拓扑排序的+Leapms模型 无圈有向图 一个图G(V,E), 如果边有向且不存在回路,则为无圈有向图.在无圈有向图上可以定义拓扑排序.下图是一个无圈有向图的例子. 拓扑排序 给定一 ...
- Java——容器类库框架浅析
前言 通常,我们总是在程序运行过程中才获得一些条件去创建对象,这些动态创建的对象就需要使用一些方式去保存.我们可以使用数组去存储,但是需要注意数组的尺寸一旦定义便不可修改,而我们并不知道程序在运行过程 ...
- RequireJS入门级_RequireJS能给我们带来什么帮助?
前言:其实很早前就已经接触和了解到RequireJS了,当时只是大概明白它能用来控制JS的加载顺序,即:页面一定要先加载这个JS,再加载那个JS,对于RequireJS的好处还没有一个真正的体会和认识 ...