最近发现了这样的java-concurrency面试任务:
使用两种方法编写简单的无锁Stack:push和pop。
我做了专注:
import java.util.concurrent.atomic.AtomicInteger;
public class Stack {
private AtomicInteger count = new AtomicInteger(-1);
private Object[] data = new Object[1000];
public void push(Object o) {
int c = count.incrementAndGet();
data[c] = o;
}
public Object pop() {
Object top;
int c;
while (true) {
c = count.get();
if (c == -1) return null;
top = data[c];
if (count.compareAndSet(c, c-1))
return top;
}
}
}
是否与预期的方法类似?或者“无锁堆栈”意味着什么不同?请帮助一位java面试新手。
答案 0 :(得分:10)
你当然开始朝着正确的方向前进,考虑使用Java的原子整数和原子函数。因此,这将是一个无锁堆栈,如:没有明确的锁。
然而,当并发访问时,它仍然是不正确的,并且证明这一点相对简单:想象你的push()线程在获取计数和将新元素添加到堆栈之间阻塞(data [c] = o),在此期间,一个pop()线程出现,获得更高的数量,并弹出......什么?无论在堆栈中该位置的内存中发生了什么,而不是Object o(因为它尚未插入)。这就是无锁,阵列支持堆栈的问题,你有两件理论上需要调整的东西,那个特定单元的数量和内容,你不能同时原子地做这两件事。我不知道那里有任何无锁阵列支持的堆栈算法。
虽然链接列表支持的堆栈算法是无锁的,但是在这种情况下,您可以创建一个新节点,为其分配内容,并且只有一个操作可以原子执行:更改顶部指针。
如果你对这个论点感兴趣,那么最好的文学作品是Shavit和Herlihy的“多处理器编程艺术”,它描述了许多不同的数据结构,包括无锁和锁定。我现在找不到任何正在详细描述“通常”无锁堆栈算法的论文,尽管Maged Michael在his SMR paper,第8页,第4.2点提及它,我自己完成了a C99 implementation
答案 1 :(得分:1)
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
public class LockFreeStack {
public static void main(String... args) {
LFStack<String> stack = new LFStack<String>();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new RandomStackUse(stack));
t.setName("My stack thread " + i);
t.start();
}
}
private static class LFStack<E> {
private volatile AtomicReference<Node<E>> head = new AtomicReference<Node<E>>();
public E peek() {
E payload = null;
Node<E> oldHeadNode = head.get();
if (oldHeadNode != null) { payload = head.get().payload; }
return payload;
}
public E pop() {
E payload;
while (true) {
Node<E> oldHeadNode = head.get();
if (oldHeadNode == null) { return null; }
payload = head.get().payload;
if (head.compareAndSet(oldHeadNode, oldHeadNode.next.get())) { break; }
//System.out.println("Retry");
}
return payload;
}
public void push(E e) {
Node<E> oldHeadNode = new Node<E>(e);
while (true) {
Node<E> oldRootNode = head.get();
if (oldRootNode != null) { oldHeadNode.next.set(oldRootNode); }
if (head.compareAndSet(oldRootNode, oldHeadNode)) { break; }
//System.out.println("Retry");
}
}
}
//to be used as LinkedList chain <Node> => <Node> => <Node> => null
private static class Node<E> {
private E payload;
private AtomicReference<Node<E>> next;
public Node(E e) {
payload = e;
next = new AtomicReference<Node<E>>();
}
}
public static class RandomStackUse implements Runnable {
private LFStack<String> stack;
private Random rand = new Random();
public RandomStackUse(LFStack<String> stack) {this.stack = stack;}
@Override
public void run() {
long counter = 0;
while (true) {
if (rand.nextInt() % 3 == 0) {
stack.push(String.valueOf(counter++));
//System.out.println(String.format("%s pushed %d", Thread.currentThread().getName(), counter));
}
if (rand.nextInt() % 3 == 1) {
String value = stack.pop();
//System.out.println(String.format("%s pop %s", Thread.currentThread().getName(), value));
}
if (rand.nextInt() % 3 == 2) {
String value = stack.peek();
//System.out.println(String.format("%s peek %s", Thread.currentThread().getName(), value));
}
}
}
}
}
答案 2 :(得分:-1)
public class MyConcurrentStack<T>
{
private AtomicReference<Node> head = new AtomicReference<Node>();
public MyConcurrentStack()
{
}
public void push(T t)
{
Node<T> n = new Node<T>(t);
Node<T> current;
do
{
current = head.get();
n.setNext(current);
}while(!head.compareAndSet(current, n));
}
public T pop()
{
Node<T> currentHead = null;
Node<T> futureHead = null;
do
{
currentHead = head.get();
if(currentHead == null)
{
return null;
}
futureHead = currentHead.next;
}while(!head.compareAndSet(currentHead, futureHead));
return currentHead.data;
}
public T peek()
{
Node<T> n = head.get();
if(n==null)
{
return null;
}
else
{
return n.data;
}
}
private static class Node<T>
{
private final T data;
private Node<T> next;
private Node(T data)
{
this.data = data;
}
private void setNext(Node next)
{
this.next = next;
}
}
public static void main(String[] args)
{
MyConcurrentStack m = new MyConcurrentStack();
m.push(12);
m.push(13);
m.push(15);
System.out.println(m.pop());
System.out.println(m.pop());
System.out.println(m.pop());
System.out.println(m.pop());
}
}
代码是自我解释的。如果有人需要解释,请告诉我。 堆栈按照下图形成:
... ... ...
| |-->| | -->| |
... ... ...
^
|
current head
答案 3 :(得分:-2)
您可以使用BlockingQueue使用方法put()插入元素和方法drainTo(Collection c)来获取元素。然后从c。
的末尾读取元素