Pelaksanaan Struktur Data LIFO Selamat Thread

1. Pengenalan

Dalam tutorial ini, kita akan membincangkan pelbagai pilihan untuk pelaksanaan struktur Data LIFO selamat Thread .

Dalam struktur data LIFO, elemen dimasukkan dan diambil mengikut prinsip Last-In-First-Out. Ini bermaksud elemen yang dimasukkan terakhir diambil dahulu.

Dalam sains komputer, tumpukan adalah istilah yang digunakan untuk merujuk kepada struktur data tersebut.

A timbunan adalah berguna untuk menangani beberapa masalah yang menarik seperti penilaian bersuara, melaksanakan operasi batal, dan lain-lain kerana ia boleh digunakan dalam persekitaran pelaksanaan serentak, kita mungkin perlu membuat ia benang-selamat.

2. Memahami Tumpukan

Pada dasarnya, Stack mesti melaksanakan kaedah berikut:

  1. tekan () - tambahkan elemen di bahagian atas
  2. pop () - ambil dan alih keluar elemen teratas
  3. mengintip () - mengambil elemen tanpa mengeluarkan dari bekas yang mendasari

Seperti yang telah dibincangkan sebelumnya, anggaplah kita mahukan mesin pemprosesan perintah.

Dalam sistem ini, mengurungkan perintah yang dilaksanakan adalah ciri penting.

Secara umum, semua arahan ditolak ke tumpukan dan kemudian operasi batalkan dapat dilaksanakan:

  • kaedah pop () untuk mendapatkan arahan terakhir yang dilaksanakan
  • panggil kaedah undo () pada objek arahan yang muncul

3. Memahami Keselamatan Benang di Tumpukan

Sekiranya struktur data tidak selamat dari benang, ketika diakses secara serentak, ia mungkin berakhir dengan keadaan perlumbaan .

Keadaan perlumbaan, secara ringkas, berlaku apabila pelaksanaan kod yang betul bergantung pada masa dan urutan utas. Ini berlaku terutamanya jika lebih daripada satu utas berkongsi struktur data dan struktur ini tidak dirancang untuk tujuan ini.

Mari kita periksa kaedah di bawah dari kelas 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; }

Untuk menjelaskan keadaan perlumbaan yang berpotensi dalam kod di atas, mari kita anggap dua utas melaksanakan kod ini seperti yang diberikan dalam urutan di bawah:

  • Benang pertama melaksanakan baris ketiga: menetapkan objek hasil dengan elemen pada indeks 'kepala'
  • Benang kedua melaksanakan baris ketiga: menetapkan objek hasil dengan elemen pada indeks 'kepala'
  • Benang pertama melaksanakan baris kelima: menetapkan semula indeks 'kepala' ke elemen seterusnya dalam susunan sokongan
  • Benang kedua melaksanakan baris kelima: mengatur semula indeks 'kepala' ke elemen seterusnya dalam susunan sokongan

Alamak! Sekarang, kedua-dua pelaksanaan akan mengembalikan objek hasil yang sama .

Untuk mengelakkan keadaan perlumbaan seperti itu, dalam hal ini, utas tidak boleh menjalankan baris pertama hingga utas yang lain selesai mengatur semula indeks 'kepala' pada baris kelima. Dengan kata lain, mengakses elemen pada 'kepala' indeks dan menetapkan semula 'kepala' indeks semestinya berlaku secara automatik untuk utas.

Jelas, dalam kes ini, pelaksanaan kod yang betul bergantung pada masa benang dan oleh itu ia tidak selamat untuk utas.

4. Tumpukan selamat benang Menggunakan Kunci

Dalam bahagian ini, kita akan membincangkan dua kemungkinan pilihan untuk pelaksanaan konkrit timbunan benang yang selamat .

Secara khusus, kami akan merangkumi Java Stack dan ArrayDeque yang dihiasi dengan selamat benang.

Kedua-duanya menggunakan Kunci untuk akses yang saling eksklusif .

4.1. Menggunakan Java Stack

Java Collections mempunyai implementasi lama untuk Stack -safe thread , berdasarkan Vector yang pada dasarnya merupakan varian ArrayList yang diselaraskan .

Walau bagaimanapun, dokumen rasmi itu sendiri mencadangkan agar mempertimbangkan untuk menggunakan ArrayDeque . Oleh itu, kita tidak akan terlalu terperinci.

Walaupun Java Stack selamat digunakan dan lurus ke hadapan, terdapat kelemahan besar dengan kelas ini:

  • Ia tidak mempunyai sokongan untuk menetapkan kapasiti awal
  • Ia menggunakan kunci untuk semua operasi. Ini mungkin menjejaskan prestasi untuk pelaksanaan single threaded.

4.2. Menggunakan ArrayDeque

Menggunakan antara muka Deque adalah pendekatan yang paling mudah untuk struktur data LIFO kerana menyediakan semua operasi timbunan yang diperlukan. ArrayDeque adalah salah satu pelaksanaan konkrit tersebut .

Oleh kerana ia tidak menggunakan kunci untuk operasi, pelaksanaan single-thread akan berfungsi dengan baik. Tetapi untuk pelaksanaan multi-thread, ini bermasalah.

Walau bagaimanapun, kami dapat melaksanakan penghias penyegerakan untuk ArrayDeque. Walaupun ini berkinerja serupa dengan kelas Stack Java Collection Framework , masalah penting kelas Stack , kekurangan pengaturan kapasitas awal, diselesaikan.

Mari lihat kelas ini:

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(); } }

Perhatikan bahawa penyelesaian kami tidak melaksanakan Deque sendiri untuk kesederhanaan, kerana ia mengandungi banyak kaedah lagi.

Juga, Jambu mengandungi SynchronizedDeque yang merupakan pelaksanaan siap produksi ArrayDequeue yang dihiasi .

5. Tumpukan selamat benang yang bebas kunci

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.

  • Untuk struktur data yang tidak dikongsi dan untuk akses utas tunggal, prestasi akan setanding dengan ArrayDeque
  • Untuk struktur data bersama, prestasi berbeza mengikut bilangan utas yang mengaksesnya secara serentak .

Dan dari segi kebolehgunaan, ia tidak berbeza dengan ArrayDeque kerana kedua-duanya melaksanakan antara muka Deque .

6. Kesimpulannya

Dalam artikel ini, kami telah membincangkan struktur data timbunan dan faedahnya dalam merancang sistem seperti mesin pemprosesan Perintah dan penilai Ekspresi.

Juga, kami telah menganalisis pelbagai implementasi timbunan dalam kerangka koleksi Java dan membincangkan prestasi dan nuansa keselamatan utas.

Seperti biasa, contoh kod boleh didapati di GitHub.