Szál biztonságos LIFO adatstruktúra megvalósítások
1. Bemutatkozás
Ebben az oktatóanyagban megvitatjuk a Thread-safe LIFO Data struktúra megvalósításának különféle lehetőségeit.
A LIFO adatstruktúrában az elemeket a Last-In-First-Out elv szerint illesztik be és kapják le. Ez azt jelenti, hogy az utoljára beillesztett elemet kapják le először.
A számítástechnikában Kazal az ilyen adatszerkezet kifejezésére használt kifejezés.
A Kazal hasznos olyan érdekes problémák kezelésében, mint a kifejezés kiértékelése, a visszavonási műveletek végrehajtása stb. Mivel párhuzamos végrehajtási környezetekben használható, előfordulhat, hogy szálbiztonságossá kell tennünk.
2. Megértés Verem
Alapvetően a Kazal a következő módszereket kell végrehajtania:
- nyom() - adjon hozzá egy elemet a tetején
- pop() - hozza le és távolítsa el a felső elemet
- kandikál() - lehívja az elemet anélkül, hogy eltávolítaná az alatta lévő tárolóból
Amint azt korábban tárgyaltuk, tegyük fel, hogy parancsfeldolgozó motort akarunk.
Ebben a rendszerben a végrehajtott parancsok visszavonása fontos jellemző.
Általánosságban elmondható, hogy az összes parancs a veremre tolódik, majd a visszavonási művelet egyszerűen végrehajtható:
- pop() metódus az utoljára végrehajtott parancs megszerzéséhez
- hívja a visszavonás () metódus a felbukkanó parancsobjektumon
3. A szálbiztonság megértése Verem
Ha egy adatstruktúra nem szálbiztos, akkor egyidejű hozzáférés esetén versenykörülmények lehetnek.
A versenyfeltételek dióhéjban akkor fordulnak elő, amikor a kód helyes végrehajtása a szálak időzítésétől és sorrendjétől függ. Ez főleg akkor történik, ha egynél több szál osztja meg az adatstruktúrát, és ezt a struktúrát nem erre a célra tervezték.
Vizsgáljuk meg az alábbi módszert egy Java Collection osztályból, ArrayDeque:
nyilvános E pollFirst () {int h = fej; E eredmény = (E) elemek [h]; // ... egyéb könyvelési műveletek eltávolítva, az egyszerűség kedvéért head = (h + 1) & (elements.length - 1); visszatérési eredmény; }
A potenciális versenyfeltételek magyarázatához a fenti kódban tegyük fel, hogy két szál hajtja végre ezt a kódot az alábbi sorrendben megadottak szerint:
- Az első szál a harmadik sort hajtja végre: az eredményobjektumot a „head” indexben lévő elemmel állítja be
- A második szál a harmadik sort hajtja végre: az eredményobjektumot a „head” indexben lévő elemmel állítja be
- Az első szál végrehajtja az ötödik sort: visszaállítja az index „fejét” a háttér tömb következő elemére
- A második szál végrehajtja az ötödik sort: visszaállítja az index „head” elemet a háttér tömb következő elemére
Hoppá! Most mindkét végrehajtás ugyanazt az eredményobjektumot adja vissza.
Az ilyen versenykörülmények elkerülése érdekében ebben az esetben egy szálnak nem szabad végrehajtania az első sort, amíg a másik szál befejezi a „head” index visszaállítását az ötödik sornál. Más szavakkal, az index „head” eleméhez való hozzáférésnek és az „head” index visszaállításának atomszerűen kell történnie egy szál esetében.
Nyilvánvaló, hogy ebben az esetben a kód helyes végrehajtása a szálak időzítésétől függ, ezért ez nem biztonságos a szálak számára.
4. Menetbiztos halmok zárak használatával
Ebben a részben a szálbiztonság konkrét megvalósításának két lehetséges lehetőségét tárgyaljuk Kazal.
Különösen kitérünk a Java-ra Kazal és szálbiztosan díszített ArrayDeque.
Mindkettő a zárakat kölcsönösen kizárja.
4.1. A Java használata Kazal
A Java Gyűjteményeknek van egy korábbi szálbiztonsági megvalósítása Kazal, alapján Vektor amely alapvetően a szinkronizált változata Tömb lista.
Maga a hivatalos doki azonban javasolja a felhasználás megfontolását ArrayDeque. Ezért nem fogunk túlságosan részletezni.
Bár a Java Kazal menetbiztos és egyenesen használható, ennek az osztálynak vannak főbb hátrányai:
- Nem támogatja a kezdeti kapacitás beállítását
- Az összes művelethez zárakat használ. Ez árthat az egyszálas végrehajtások teljesítményének.
4.2. Használata ArrayDeque
Használni a Deque Az interfész a legkényelmesebb megközelítés a LIFO adatstruktúrák számára, mivel biztosítja az összes szükséges veremműveletet.ArrayDeque egy ilyen konkrét megvalósítás.
Mivel a műveletekhez nem használ zárakat, az egyszálú végrehajtások nagyon jól működnek. De a többszálú kivégzéseknél ez problematikus.
Szinkronizáló dekorátort azonban megvalósíthatunk a ArrayDeque. Bár ez hasonlóan működik, mint a Java Collection Framework Kazal osztály, a fontos kérdés Kazal osztály, a kezdeti kapacitásbeállítás hiánya megoldott.
Vessünk egy pillantást erre az osztályra:
public class DequeBasedSynchronizedStack {// belső Deque, amelyet a szinkronizáláshoz díszítenek. magán ArrayDeque dequeStore; public DequeBasedSynchronizedStack (int initialCapacity) {this.dequeStore = new ArrayDeque (kezdeti kapacitás); } public DequeBasedSynchronizedStack () {dequeStore = new ArrayDeque (); } nyilvános szinkronizált T pop () {return this.dequeStore.pop (); } nyilvános szinkronizált void push (T elem) {this.dequeStore.push (elem); } nyilvános szinkronizált T peek () {return this.dequeStore.peek (); } public synchronized int size () {return this.dequeStore.size (); }}
Ne feledje, hogy megoldásunk nem valósul meg Deque maga az egyszerűség kedvéért, mivel sokkal több módszert tartalmaz.
Továbbá, Guava tartalmaz SynchronizedDeque amely a díszítés gyártásra kész megvalósítása ArrayDequeue.
5. Zármentes szálbiztos verem
ConcurrentLinkedDeque zár nélküli megvalósítása Deque felület. Ez a megvalósítás teljesen menetes mivel hatékony zármentes algoritmust használ.
A zár nélküli megvalósítások immunisak a következő problémákkal szemben, ellentétben a zár alapúakkal.
- Prioritás inverzió - Ez akkor fordul elő, amikor az alacsony prioritású szál tartja a magas prioritású szál számára szükséges zárat. Ez a magas prioritású szál blokkolását okozhatja
- Holtpontok - Ez akkor fordul elő, amikor a különböző szálak ugyanazt az erőforráskészletet más sorrendben zárolják.
Ráadásul a zár nélküli megvalósításoknak vannak olyan funkcióik, amelyek tökéletesen használhatók mind egy-, mind többszálas környezetben.
- Nem megosztott adatstruktúrákhoz és egyszálú hozzáférés, a teljesítmény egyenlő lenne ArrayDeque
- Megosztott adatstruktúrák esetén a teljesítmény az egyidejűleg hozzáférő szálak számától függ.
A használhatóság szempontjából pedig nem más, mint ArrayDeque mivel mindkettő megvalósítja a Deque felület.
6. Következtetés
Ebben a cikkben megvitattuk a Kazal az adatstruktúra és annak előnyei olyan rendszerek tervezésében, mint a Command feldolgozó motor és az Expression kiértékelők.
Elemeztük a Java gyűjtemény keretrendszerének különféle megvalósításait, és megvitattuk azok teljesítményét és szálbiztonsági árnyalatait.
Szokás szerint kódpéldák találhatók a GitHubon.