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

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

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

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

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

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. CDI服务

    前言 CDI(Contexts and Dependency Injection 上下文依赖注入),是JAVA官方提供的依赖注入实现,可用于Dynamic Web Module中,将依赖注入IOC/D ...

  2. SUSE12SP3-Zookeeper安装

    直接使用root账号 1.zookeeper安装 将zookeeper-3.4.13.tar.gz安装包放置指定目录 sudo tar -zxvf zookeeper-3.4.13.tar.gz -C ...

  3. 三种方式实现观察者模式 及 Spring中的事件编程模型

    观察者模式可以说是众多设计模式中,最容易理解的设计模式之一了,观察者模式在Spring中也随处可见,面试的时候,面试官可能会问,嘿,你既然读过Spring源码,那你说说Spring中运用的设计模式吧, ...

  4. spring beans源码解读之--总结篇

    spring beans下面有如下源文件包: org.springframework.beans, 包含了操作java bean的接口和类.org.springframework.beans.anno ...

  5. Java设计模式系列-工厂方法模式

    原创文章,转载请标注出处:<Java设计模式系列-工厂方法模式> 一.概述 工厂,就是生产产品的地方. 在Java设计模式中使用工厂的概念,那就是生成对象的地方了. 本来直接就能创建的对象 ...

  6. Java设计模式系列-装饰器模式

    原创文章,转载请标注出处:<Java设计模式系列-装饰器模式> 一.概述 装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能. 不同于适配器模式和桥接模式,装饰器模式涉及的是 ...

  7. [Javascript] js的类和对象

    类 graph LR 类-->构造函数 类-->prototype对象 类-->instanceof运算符 类-->constructor属性 类-->isPrototy ...

  8. 使用wepy开发微信小程序01——常用的组件

    1.axio.js 'use strict' import axios from 'axios' import wepyAxiosAdapter from 'wepy-plugin-axios/dis ...

  9. Excel自动换行、Export2Excel 自动换行

    1需求:导出excel后自动换行显示. 2插件:Export2Excel 3.测试 listToExcel() { import('@/vendor/Export2Excel').then(excel ...

  10. kali linux 2019.1 替换burpsuite pro 1.7.37

    下面全部实操: 先切换JDK版本为1.8.执行 update-alternatives --config java 然后会显示多个jdk版本,1.8选3 输入java –version会显示jdk为1 ...