这道题是我亲身经历的一道大厂面试题,非常值得分享!

这道题可以分为两个步骤进行编码解答,第一步是基于数组实现一个队列,第二步是实现线程阻塞。

如果是基于数组实现栈的数据结构,那么我们只需要一个指针进行来回移动即可。

想象一下,脑海中有一个竖立起来的栈,指针上移代表元素进栈,指针下移,代表元素出栈,整个过程只需要一个指针进行上下移动即可。

由此可以写出下面的代码:

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面试题:使用数组实现一个简单的阻塞队列的更多相关文章

  1. 自己动手系列----使用数组实现一个简单的Map

    数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同.Java 语言中提供的数组是用来存储固定大小的同类型元素. 这里提一下,数组的优缺点: 优点: 1. 使用索 ...

  2. Java编程的逻辑 (61) - 内存映射文件及其应用 - 实现一个简单的消息队列

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  3. Java实现一个简单的循环队列

    在某些时候,我们不能被要求像数组一样可以使用索引随机访问,而是需要被限制顺序处理业务,今天介绍一种先进先出(FIFO)的线性数据结构:队列, 当然,还有后进先出(LIFO)的处理方式,即为栈(后续有时 ...

  4. 面试之路(8)-BAT面试题之数组和链表的区别

    两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用 各自的特点: 数组: 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素.但是如果要在数组中增加一个 ...

  5. 自己动手系列----使用数组实现一个简单的Set

    Set:注重独一无二的性质,该体系集合可以知道某物是否已近存在于集合中,不会存储重复的元素用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复.主要有HashSet和TreeSet两大实现类. ...

  6. 演示一个简单的Redis队列

    0.Windows Service版下载 https://github.com/rgl/redis/downloads 1.新建一个Console项目 打开Nuget控制台,执行以下命令 Instal ...

  7. 一个简单的js队列,逻辑很清晰

    function Queue(type) { //type 是否是一个接着一个执行 function QueueConst() {} QueueConst.execute_ing=[], QueueC ...

  8. 教你如何使用Java手写一个基于链表的队列

    在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...

  9. nodejs一个函数实现消息队列中间件

    消息队列中间件(Message Queue)相信大家不会陌生,如Kafka.RabbitMQ.RocketMQ等,已经非常成熟,在大大小小的公司和项目中也已经广泛使用. 有些项目中,如果是只使用初步的 ...

随机推荐

  1. webgl自学笔记——深度监测与混合

    这一章中关于webgl中颜色的使用我们将深入研究.我们将从研究颜色在webgl和essl中如何被组装和获取开始.然后我们讨论在物体.光照和场景中颜色的使用.这之后我们将看到当一个物体在另一个物体前面是 ...

  2. Nodejs+Express 搭建 web应用

    简单的记录下关于如何使用nodejs+Express 极速搭建一个web应用. 项目所需,要用到nodejs,那就去学咯.简单的看了下 七天学会NodeJS,Node.js 教程.发现其实好简单的,分 ...

  3. RecyclerView和ListView比较

    题记: RecyclerView说是目前最重要的控件也不为过,ListView虽然被RecyclerView光芒掩盖,但也仍有着自己的d地位:这个问题不是很偏重原理或实践,而更多的是,针对面试中会问到 ...

  4. 学习ASP.NET Core Razor 编程系列十一——把新字段更新到数据库

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  5. .Net Core Web Api 上传女朋友的照片到微软云Azure Storage

    前言 实现一个Web Api,把女朋友照片保存到Azure云的storage里. Image Upload Api 在对应的Api Controller里,加上attribute: [Consumes ...

  6. 深入解读MySQL8.0 新特性 :Crash Safe DDL

    前言 在MySQL8.0之前的版本中,由于架构的原因,mysql在server层使用统一的frm文件来存储表元数据信息,这个信息能够被不同的存储引擎识别.而实际上innodb本身也存储有元数据信息.这 ...

  7. SLAM+语音机器人DIY系列:(二)ROS入门——8.理解roslaunch在大型项目中的作用

    摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...

  8. SLAM+语音机器人DIY系列:(四)差分底盘设计——3.底盘通信协议

    摘要 运动底盘是移动机器人的重要组成部分,不像激光雷达.IMU.麦克风.音响.摄像头这些通用部件可以直接买到,很难买到通用的底盘.一方面是因为底盘的尺寸结构和参数是要与具体机器人匹配的:另一方面是因为 ...

  9. 2017年IT行业测试调查报告

    在刚刚过去的2017年, 我们来一起看一下2017年IT行业测试调查报告 还是1到5名测试工程师最多 Test Architects 在北上广一线城市已经出现 https://www.lagou.co ...

  10. asp.net easyui 动态绑定下拉框

    前台: <title>标题</title> <link href="EasyUi_v1.3.4/easyui/themes/default/easyui.css ...