面试阿里前,java程序员问问自己能不能手写这道题

正文

这道题是我亲身经历的一道阿里面试题,分享一下!

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

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

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

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

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线程

尾声

更多程序员的日常,编程语言学习,面试经验,欢迎关注我,在历史文章中查看~~

也欢迎同行们加入我的群:714526711私聊胡侃哦~群内有资料领取哦

热门框架源码学习

微服务架构实战

系统性能优化

高并发和分布式技术

电商项目实战

容器化技术专题

搜索引擎

高级架构师.png

面试资料.png

数据库.png

欢迎工作一到五年的Java工程师朋友们加入java/大数据群:714526711
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
 

猜你喜欢

转载自blog.csdn.net/javaxueyuan_yezi/article/details/89246964
今日推荐