簡體   English   中英

簡單的無鎖棧

[英]Simple Lock-Free Stack

最近發現了這樣的java並發面試任務:

使用兩種方法編寫簡單的無鎖 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面試新手。

您肯定已經朝着正確的方向開始,考慮使用 Java 的原子整數和原子函數。 因此,這將是一個無鎖堆棧,如:沒有顯式鎖。

然而,並發訪問時它仍然不正確,並且證明這一點相對簡單:想象一下你的 push() 線程在獲取計數和將新元素添加到堆棧之間阻塞 (data[c] = o),並且在與此同時,一個 pop() 線程出現,獲得更高的計數,然后彈出......什么? 無論發生在堆棧中那個位置的內存中,而不是 Object o (因為它尚未插入)。

這就是無鎖、數組支持的堆棧的問題,理論上你需要調整兩件事,即特定單元格的計數和內容,並且不能同時以原子方式進行。 我不知道那里有任何無鎖數組支持的堆棧算法。

盡管有鏈表支持的堆棧算法是無鎖的,因為在這種情況下,您可以創建一個新節點,為其分配內容,並且您只有一個原子地執行的操作:更改頂部指針。

如果您對這個論點感興趣,最好的文學作品是 Shavit 和 Herlihy 的“多處理器編程藝術”,它描述了許多不同的數據結構,包括無鎖和基於鎖的。 我現在找不到任何詳細描述“通常”無鎖堆棧算法的論文,盡管 Maged Michael 在 他的 SMR 論文第 8 頁第 4.2 點中提到了它,而且我自己已經完成了 C99 實現

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> node = head.get();
        if (node != null) { payload = node.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));
            }
        }
    }
}
}
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

您可以使用BlockingQueue使用方法 put() 插入元素並使用方法 drainTo(Collection c) 獲取元素。 然后從c的末尾讀取元素。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM