Внедряване на структурата на данните за безопасна LIFO тема

1. Въведение

В този урок ще обсъдим различни опции за реализацията на структурата на данните LIFO, безопасна за нишки .

В структурата на данни LIFO елементите се вмъкват и извличат в съответствие с принципа Last-In-First-Out. Това означава, че последният вмъкнат елемент се извлича първи.

В компютърните науки стекът е терминът, използван за означаване на такава структура на данните.

А стека е удобен да се справят с някои интересни проблеми като оценка изразяване, изпълнение Отмяна операции и т.н. Тъй като тя може да се използва в едновременни среда за изпълнение, ние може да се наложи да го вденете-безопасно.

2. Разбиране на стековете

По принцип стекът трябва да прилага следните методи:

  1. push () - добавяне на елемент отгоре
  2. pop () - извличане и премахване на горния елемент
  3. peek () - извличане на елемента, без да се изважда от основния контейнер

Както беше обсъдено по-горе, нека приемем, че искаме механизъм за обработка на команди.

В тази система отмяната на изпълнените команди е важна характеристика.

По принцип всички команди се изтласкват върху стека и след това операцията за отмяна може просто да бъде приложена:

  • pop () метод за получаване на последната изпълнена команда
  • извикайте метода undo () на изскачащия команден обект

3. Разбиране на безопасността на нишките в стекове

Ако структурата на данните не е безопасна за нишки, при едновременен достъп до нея може да има условия на състезание .

Състезателни условия, накратко, възникват, когато правилното изпълнение на кода зависи от времето и последователността на нишките. Това се случва главно, ако повече от една нишка споделя структурата на данните и тази структура не е предназначена за тази цел.

Нека разгледаме метод по-долу от клас Java Collection, ArrayDeque :

public E pollFirst() { int h = head; E result = (E) elements[h]; // ... other book-keeping operations removed, for simplicity head = (h + 1) & (elements.length - 1); return result; }

За да обясним потенциалното състезателно състояние в горния код, нека приемем две нишки, изпълняващи този код, както е дадено в следващата последователност:

  • Първата нишка изпълнява третия ред: задава резултантния обект с елемента в индекса 'head'
  • Втората нишка изпълнява третия ред: задава обекта на резултата с елемента в индекса 'head'
  • Първата нишка изпълнява петия ред: нулира индекса „head“ към следващия елемент в подложния масив
  • Втората нишка изпълнява петия ред: нулира индекса „head“ към следващия елемент в подложния масив

Ами сега! Сега и двете изпълнения биха върнали един и същ обект на резултата .

За да се избегнат такива състезателни условия, в този случай нишка не трябва да изпълнява първия ред, докато другата нишка не завърши нулирането на индекса „head“ на петия ред. С други думи, достъпът до елемента в индекса „head“ и нулирането на индекса „head“ трябва да се случи атомично за нишка.

Ясно е, че в този случай правилното изпълнение на кода зависи от времето на нишките и следователно не е безопасно за нишки.

4. Безопасни за нишките стекове, използващи брави

В този раздел ще обсъдим две възможни опции за конкретни реализации на стек, безопасен за нишки .

По-специално ще разгледаме Java Stack и безопасно декодиран ArrayDeque.

И двамата използват ключалки за взаимно изключващ се достъп .

4.1. Използване на Java стека

Java Collections има наследствена реализация за стек -безопасен стек , базиран на Vector, който по същество е синхронизиран вариант на ArrayList.

Самият официален документ обаче предлага да се обмисли използването на ArrayDeque . Следователно няма да навлизаме в твърде много подробности.

Въпреки че Java Stack е безопасен за нишки и е лесен за използване, има основни недостатъци при този клас:

  • Той няма поддръжка за настройка на първоначалния капацитет
  • Той използва ключалки за всички операции. Това може да навреди на производителността при изпълнения с една нишка.

4.2. Използване на ArrayDeque

Използването на интерфейса Deque е най-удобният подход за структурите на данни LIFO, тъй като осигурява всички необходими операции на стека. ArrayDeque е едно такова конкретно изпълнение .

Тъй като не използва брави за операциите, изпълненията с една нишка биха работили добре. Но за многонишковите изпълнения това е проблематично.

Въпреки това можем да приложим декоратор за синхронизация за ArrayDeque. Въпреки че това работи подобно на класа на стека на Java Collection Framework , важният проблем на класа на стека , липсата на първоначална настройка на капацитета, е решен.

Нека да разгледаме този клас:

public class DequeBasedSynchronizedStack { // Internal Deque which gets decorated for synchronization. private ArrayDeque dequeStore; public DequeBasedSynchronizedStack(int initialCapacity) { this.dequeStore = new ArrayDeque(initialCapacity); } public DequeBasedSynchronizedStack() { dequeStore = new ArrayDeque(); } public synchronized T pop() { return this.dequeStore.pop(); } public synchronized void push(T element) { this.dequeStore.push(element); } public synchronized T peek() { return this.dequeStore.peek(); } public synchronized int size() { return this.dequeStore.size(); } }

Имайте предвид, че нашето решение не прилага самото Deque за простота, тъй като съдържа много повече методи.

Също така, Guava съдържа SynchronizedDeque, което е готово за изпълнение изпълнение на декориран ArrayDequeue.

5. Без заключване нишки, безопасни за нишки

ConcurrentLinkedDeque is a lock-free implementation of Deque interface. This implementation is completely thread-safe as it uses an efficient lock-free algorithm.

Lock-free implementations are immune to the following issues, unlike lock based ones.

  • Priority inversion – This occurs when the low-priority thread holds the lock needed by a high priority thread. This might cause the high-priority thread to block
  • Deadlocks – This occurs when different threads lock the same set of resources in a different order.

On top of that, Lock-free implementations have some features which make them perfect to use in both single and multi-threaded environments.

  • За несподелени структури от данни и за достъп с една нишка , производителността ще бъде равна на ArrayDeque
  • За споделените структури от данни производителността варира в зависимост от броя нишки, които имат достъп до нея едновременно .

И по отношение на използваемостта, то е по-различно от ArrayDeque едновременно прилагане на Deque интерфейс.

6. Заключение

В тази статия обсъдихме структурата на данните за стека и нейните предимства при проектирането на системи като механизъм за обработка на команди и оценители на Expression.

Също така анализирахме различни реализации на стека в рамката на колекциите на Java и обсъдихме тяхната производителност и нюанси за безопасност на нишките.

Както обикновено, примери за кодове могат да бъдат намерени в GitHub.