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

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

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

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

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

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. Python存储系统(Redis)

    存储系统数据缓存一般会使用三个模块:Mongodb,redis,memcache.其中memcache是轻量级缓存,只能将数据保存到内存中,redis可以配置数据保存在内存还是硬盘. 其主要用途有:不 ...

  2. Java基础--JDK的安装和配置

    一.Java平台简述 1.1 Java语言简介 Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两 ...

  3. Snipaste多截屏工具软件

    Snipaste是一个简单的截图小工具,可以支持同时截取多张图片,添加标注等等,操作简单方便,解压即用 百度网盘链接: https://pan.baidu.com/s/1YC75DRoLzdeyli1 ...

  4. 【Hadoop篇】--Hadoop常用命令总结

    一.前述 分享一篇hadoop的常用命令的总结,将常用的Hadoop命令总结如下. 二.具体 1.启动hadoop所有进程start-all.sh等价于start-dfs.sh + start-yar ...

  5. Swagger如何访问Ocelot中带权限验证的API

    先亮源代码:https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/SwaggerDe ...

  6. ToastMiui【仿MIUI的带有动画的Toast】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 仿MIUI的带有动画的Toast 效果图 代码分析 ToastMiui类基于WindowManager 为了和Toast用法保持一致 ...

  7. bootsect及setup

      BIOS和bootsect CPU加电即进入16位实模式 硬件逻辑设计为加电瞬间强行设置:CS=0xF000,IP=0xFFF0,CS:IP=0xFFFF0 而BIOS程序的入口地址即0xFFFF ...

  8. Mondrian + JPivot 环境配置

    一.环境准备 特别说明:Mondrian + JPivot 环境笔者已整理调试通过,可直接部署运行. 1.1 环境要求 JDK1.8+ 1.2 环境包说明 从 https://pan.baidu.co ...

  9. 在ASP.NET Core中构建路由的5种方法

    原文链接 :https://stormpath.com/blog/routing-in-asp-net-core 在ASP.NET Core中构建路由的5种方法 原文链接 :https://storm ...

  10. IL中间语言指令大全

    一些 IL 语言指令解释: Public field Static     Beq     如果两个值相等,则将控制转移到目标指令.Public field Static     Beq_S     ...