面试官: 如何用Java实现一个栈?
Posted Dream_it_possible!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官: 如何用Java实现一个栈?相关的知识,希望对你有一定的参考价值。
目录
有很多人在及技术面试的时候经常会被各种刁钻问题给灵魂拷问到,怎样去实现一个数据结构? 因为我们平时用java现成的数据结构屡试不爽,这个时候聪明的小脑袋可能会卡壳~
由于栈是一个先进后出的数据结构,我们要实现它得从入栈和出栈两个方面重点入手。
一、使用单链表结构实现
主要思想: 入栈采用尾插法,出栈直接取最后一个节点,然后将最后一个节点从链表中移除即可。
如图:
package collection;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/20 0020 17:37
*/
public class Entry<T>
Entry<T> next;
T data;
public Entry(Entry<T> next, T data)
this.next = next;
this.data = data;
public T getData()
return data;
MyUnSafeStack:
package collection;
import cn.hutool.core.thread.ExecutorBuilder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Desc: 非线程安全
* @Author: bingbing
* @Date: 2022/4/20 0020 17:37
*/
public class MyUnSafeStack<T>
private AtomicInteger size = new AtomicInteger(0);
/**
* 采用尾插法
*/
private Entry<T> lastNode;
public int size()
return size.get();
public boolean isEmpty()
return size() <= 0;
public void push(T element)
Entry<T> node;
if (lastNode == null)
node = new Entry<>(null, element);
else
node = new Entry<>(lastNode, element);
lastNode = node;
size.incrementAndGet();
/**
* 弹出最后一个节点
*
* @return
*/
public T pop()
if (size.get() <= 0)
return null;
size.decrementAndGet();
// 移除末尾节点
T data = lastNode.getData();
lastNode = lastNode.next;
return data;
public static void main(String[] args)
MyUnSafeStack<Integer> stack = new MyUnSafeStack<>();
for (int i = 0; i < 20; i++)
stack.push(i);
while (!stack.isEmpty())
System.out.println("出栈:" + stack.pop());
打印结果:
面试官看了笑了笑,实现了基本的功能,但是多线程环境下,对单链表的读写存在线程安全的问题,你考虑到了嘛?
我机灵的小脑袋灵光一闪,给它加把锁!
二、单链表+ReentrantLock
在push和pop操作前使用reentrantlock加锁。
package collection;
import cn.hutool.core.thread.ExecutorBuilder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Desc: 线程安全 reentrantLock
* @Author: bingbing
* @Date: 2022/4/20 0020 17:37
*/
public class MyLockStack<T>
private AtomicInteger size = new AtomicInteger(0);
private ReentrantLock lock = new ReentrantLock();
/**
* 采用尾插法
*/
private Entry<T> lastNode;
public int size()
return size.get();
public boolean isEmpty()
return size() <= 0;
public void push(T element)
lock.lock();
try
Entry<T> node;
if (lastNode == null)
node = new Entry<>(null, element);
else
node = new Entry<>(lastNode, element);
System.out.println("入栈:" + node.getData());
lastNode = node;
size.incrementAndGet();
finally
lock.unlock();
/**
* 弹出最后一个节点
*
* @return
*/
public T pop()
lock.lock();
try
if (size.get() <= 0)
return null;
size.decrementAndGet();
// 移除末尾节点
T data = lastNode.getData();
lastNode = lastNode.next;
return data;
finally
lock.unlock();
public static void main(String[] args)
MyLockStack<Integer> stack = new MyLockStack<>();
ExecutorService executorService = ExecutorBuilder.create().build();
for (int i = 0; i < 20; i++)
final int index = i;
executorService.execute(() ->
stack.push(index);
);
try
Thread.sleep(1000L);
catch (InterruptedException e)
e.printStackTrace();
while (!stack.isEmpty())
System.out.println("出栈:" + stack.pop());
打印结果:
发现入栈的顺序并不是按照i的递增进行入栈的, 因为出现竞争锁的情况!
面试官看了后点了点头,嗯嗯..... 加锁可以避免线程安全的问题,但是会损耗一定的竞争性能,请问还有其他方式实现替代加锁嘛?
于是我又想到了另外一个方法,使用CAS操作就能避免加锁了。
三、使用CAS实现一个非阻塞的栈
主要思想: 在入栈时,取到lastNode为期望的数据,插入的数据为要更新的数据,如果期间又其他线程修改掉了lastNode,那么cas不成功,会重新进行CAS!
public void push(T element)
Entry<T> node = new Entry<>(null, element);
Entry<T> old;
do
old = lastNode.get();
node.next = old;
while (!lastNode.compareAndSet(old, node));
size.incrementAndGet();
出栈时,我们先拿到lastNode, 因为有要移除节点的操作,因此需要重新将lastNode的next重新赋值给lastNode。
public T pop()
if (size.get() <= 0)
return null;
Entry<T> top;
Entry<T> topNext;
do
top = lastNode.get();
topNext = top.next;
while (!lastNode.compareAndSet(top, topNext));
size.decrementAndGet();
return top.getData();
完整代码:
package collection;
import cn.hutool.core.thread.ExecutorBuilder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Desc: 线程安全 CASLock
* @Author: bingbing
* @Date: 2022/4/20 0020 17:37
*/
public class MyCASStack<T>
private AtomicInteger size = new AtomicInteger(0);
/**
* 采用尾插法
*/
private AtomicReference<Entry<T>> lastNode = new AtomicReference<>();
public int size()
return size.get();
public boolean isEmpty()
return size() <= 0;
/**
* @param element
*/
public void push(T element)
Entry<T> node = new Entry<>(null, element);
Entry<T> old;
do
old = lastNode.get();
node.next = old;
while (!lastNode.compareAndSet(old, node));
size.incrementAndGet();
/**
* 弹出最后一个节点
*
* @return
*/
public T pop()
if (size.get() <= 0)
return null;
Entry<T> top;
Entry<T> topNext;
do
top = lastNode.get();
topNext = top.next;
while (!lastNode.compareAndSet(top, topNext));
size.decrementAndGet();
return top.getData();
public static void main(String[] args)
MyCASStack<Integer> stack = new MyCASStack<>();
ExecutorService executorService = ExecutorBuilder.create().build();
for (int i = 0; i < 20; i++)
final int index = i;
executorService.execute(() ->
stack.push(index);
);
try
Thread.sleep(1000L);
catch (InterruptedException e)
e.printStackTrace();
while (!stack.isEmpty())
System.out.println("出栈:" + stack.pop());
executorService.shutdown();
打印结果:
统计耗时
将Lock和CAS做比较,设置任务数为5000
Lock 耗时:
CAS耗时:
当任务数越多时,CAS操作耗时< Lock操作的耗时 就越明显。
面试官看到了最后,嗯嗯... 小伙子等后续通知把~
继续进行后面的面试。。。
以上是关于面试官: 如何用Java实现一个栈?的主要内容,如果未能解决你的问题,请参考以下文章