Pengenalan kepada Lock Striping

1. Pengenalan

Dalam tutorial ini, kita akan belajar bagaimana mencapai penyegerakan halus, juga dikenali sebagai Lock Striping, corak untuk menangani akses serentak ke struktur data sambil mengekalkan prestasi yang baik.

2. Masalahnya

HashMap bukan struktur data yang selamat digunakan kerana sifatnya yang tidak diselaraskan. Itu bermaksud perintah dari persekitaran multi-utas mungkin mengakibatkan ketidakkonsistenan data.

Untuk mengatasi masalah itu, kita boleh menukar peta asal dengan kaedah Collections # synchronizedMap atau menggunakan struktur data HashTable . Kedua-duanya akan mengembalikan pelaksanaan antara muka Peta yang selamat , tetapi mereka memerlukan kos prestasi.

Pendekatan menentukan akses eksklusif ke atas struktur data dengan objek kunci tunggal disebut penyegerakan kasar .

Dalam pelaksanaan penyelarasan kasar, setiap akses ke objek mesti dibuat satu demi satu dengan satu utas. Kami akhirnya mempunyai akses berurutan.

Tujuan kami adalah untuk membolehkan utas serentak berfungsi pada struktur data sambil memastikan keselamatan utas.

3. Jalur Kunci

Untuk mencapai matlamat kami, kami akan menggunakan corak Lock Striping. Jalur kunci adalah teknik di mana penguncian berlaku pada beberapa baldi atau jalur, yang bermaksud bahawa mengakses baldi hanya mengunci baldi itu dan bukan keseluruhan struktur data.

Terdapat beberapa cara untuk melakukan ini:

  • Pertama, kita dapat menggunakan kunci per tugas, sehingga memaksimumkan kesesuaian antara tugas - ini mempunyai jejak memori yang lebih tinggi,
  • Atau, kita dapat menggunakan satu kunci untuk setiap tugas, yang menggunakan memori yang lebih sedikit tetapi juga menjejaskan prestasi secara bersamaan

Untuk membantu kami menguruskan pertukaran prestasi-memori ini, Jambu Batu dihantar dengan kelas bernama Striped. Ini serupa dengan logik yang terdapat di ConcurrentHashMap , tetapi kelas Striped semakin jauh dengan mengurangkan penyegerakan tugas yang berbeza menggunakan semaphores atau kunci reentrant.

4. Contoh Pantas

Mari buat contoh ringkas untuk membantu kita memahami faedah corak ini.

Kami akan membandingkan HashMap vs ConcurrentHashMap dan satu kunci vs kunci berjalur yang menghasilkan empat eksperimen.

Untuk setiap eksperimen, kami akan melakukan pembacaan dan penulisan serentak di Peta yang mendasari . Yang akan berbeza adalah bagaimana kita mengakses setiap baldi.

Dan untuk itu, kami akan membuat dua kelas - SingleLock dan StripedLock. Ini adalah pelaksanaan konkrit dari kelas abstrak ConcurrentAccessExperiment yang berfungsi.

4.1. Kebergantungan

Oleh kerana kita akan menggunakan Guava ini Striped kelas, kami akan menambah jambu pergantungan:

 com.google.guava guava 28.2-jre 

4.2. Proses Utama

Kelas ConcurrentAccessExperiment kami melaksanakan tingkah laku yang telah dijelaskan sebelumnya:

public abstract class ConcurrentAccessExperiment { public final Map doWork(Map map, int threads, int slots) { CompletableFuture[] requests = new CompletableFuture[threads * slots]; for (int i = 0; i < threads; i++) { requests[slots * i + 0] = CompletableFuture.supplyAsync(putSupplier(map, i)); requests[slots * i + 1] = CompletableFuture.supplyAsync(getSupplier(map, i)); requests[slots * i + 2] = CompletableFuture.supplyAsync(getSupplier(map, i)); requests[slots * i + 3] = CompletableFuture.supplyAsync(getSupplier(map, i)); } CompletableFuture.allOf(requests).join(); return map; } protected abstract Supplier putSupplier(Map map, int key); protected abstract Supplier getSupplier(Map map, int key); }

Penting untuk diperhatikan bahawa, kerana ujian kami terikat pada CPU, kami telah mengehadkan bilangan keranjang kepada beberapa pemproses yang ada.

4.3. Akses Serentak dengan ReentrantLock

Sekarang kita akan melaksanakan kaedah untuk tugas tak segerak kita.

Kelas SingleLock kami menentukan satu kunci untuk keseluruhan struktur data menggunakan ReentrantLock :

public class SingleLock extends ConcurrentAccessExperiment { ReentrantLock lock; public SingleLock() { lock = new ReentrantLock(); } protected Supplier putSupplier(Map map, int key) { return (()-> { lock.lock(); try { return map.put("key" + key, "value" + key); } finally { lock.unlock(); } }); } protected Supplier getSupplier(Map map, int key) { return (()-> { lock.lock(); try { return map.get("key" + key); } finally { lock.unlock(); } }); } }

4.4. Akses Serentak dengan Bergaris

Kemudian, kelas StripedLock menentukan kunci bergaris untuk setiap baldi:

public class StripedLock extends ConcurrentAccessExperiment { Striped lock; public StripedLock(int buckets) { lock = Striped.lock(buckets); } protected Supplier putSupplier(Map map, int key) { return (()-> { int bucket = key % stripedLock.size(); Lock lock = stripedLock.get(bucket); lock.lock(); try { return map.put("key" + key, "value" + key); } finally { lock.unlock(); } }); } protected Supplier getSupplier(Map map, int key) { return (()-> { int bucket = key % stripedLock.size(); Lock lock = stripedLock.get(bucket); lock.lock(); try { return map.get("key" + key); } finally { lock.unlock(); } }); } }

Jadi, strategi mana yang lebih baik?

5. Hasil

Mari gunakan JMH (Java Microbenchmark Harness) untuk mengetahui. Tanda aras boleh didapati melalui pautan kod sumber pada akhir tutorial.

Berdasarkan penanda aras kami, kami dapat melihat sesuatu yang serupa dengan yang berikut (perhatikan bahawa throughput yang lebih tinggi lebih baik):

Benchmark Mode Cnt Score Error Units ConcurrentAccessBenchmark.singleLockConcurrentHashMap thrpt 10 0,059 ± 0,006 ops/ms ConcurrentAccessBenchmark.singleLockHashMap thrpt 10 0,061 ± 0,005 ops/ms ConcurrentAccessBenchmark.stripedLockConcurrentHashMap thrpt 10 0,065 ± 0,009 ops/ms ConcurrentAccessBenchmark.stripedLockHashMap thrpt 10 0,068 ± 0,008 ops/ms 

6. Kesimpulan

Dalam tutorial ini, kami meneroka pelbagai cara bagaimana kami dapat mencapai prestasi yang lebih baik menggunakan Lock Striping dalam struktur seperti Peta . Kami membuat penanda aras untuk membandingkan hasilnya dengan beberapa pelaksanaan.

Dari hasil penanda aras kami, kami dapat memahami bagaimana strategi serentak yang berbeza dapat mempengaruhi proses keseluruhan secara signifikan. Corak Striped Lock memberikan penambahbaikan kerana mendapat markah ~ 10% tambahan dengan HashMap dan ConcurrentHashMap .

Seperti biasa, kod sumber untuk tutorial ini boleh didapati di GitHub.