Простой Стек Без Блокировки
Недавно нашли такую задачу Java-concurrency interview:
Напишите простой стек без блокировки с помощью двух методов: 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-интервью.
4 ответа:
Вы определенно начали в правильном направлении, думая об использовании атомарных целых чисел и атомарных функций Java. Таким образом, это будет стек без блокировки, как в: нет явных блокировок.
Это все еще не правильно при одновременном доступе, однако, и это относительно просто продемонстрировать: представьте, что ваш push() поток блокирует между получением count и добавлением нового элемента в стек (data[c] = o), а тем временем появляется pop() поток, получает более высокий уровень граф, и хлопает... Что? Все, что находится в памяти в этом месте стека, но не объект o (потому что он еще не был вставлен).
И это проблема с незащищенными стеками с поддержкой массивов, что у вас есть две вещи, которые вам теоретически нужно настроить, количество и содержимое этой конкретной ячейки, и вы не можете сделать оба атома одновременно. Я не знаю ни одного алгоритма стека с поддержкой массивов без блокировки.
Существуют стеки с поддержкой связанных списков алгоритмы, однако, не имеют блокировок, потому что в этом случае вы можете создать новый узел, назначить ему содержимое, и у вас есть только одна операция для атомарного выполнения: изменение верхнего указателя.
Если вас интересует этот аргумент, лучшей литературной работой является книга Шавита и Херлихи "искусство многопроцессорного программирования", в которой описывается множество различных структур данных, как без блокировки, так и на основе блокировки. Я не могу найти сейчас ни одной статьи, описывающей" обычный " алгоритм без блокировки стека в подробно, хотя Магед Майкл упоминает об этом в своей 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> 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)); } } } } }
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(коллекция С), чтобы получить элементы. Затем считайте элементы из конца c.