Pengantar Koleksi Java Tersinkronisasi

1. Ikhtisar

Kerangka koleksi adalah komponen kunci Java. Ini menyediakan sejumlah besar antarmuka dan implementasi, yang memungkinkan kita untuk membuat dan memanipulasi berbagai jenis koleksi secara langsung.

Meskipun menggunakan koleksi biasa yang tidak disinkronkan sederhana secara keseluruhan, ini juga bisa menjadi proses yang menakutkan dan rawan kesalahan saat bekerja di lingkungan multi-utas (alias pemrograman bersamaan).

Oleh karena itu, platform Java menyediakan dukungan yang kuat untuk skenario ini melalui pembungkus sinkronisasi yang berbeda yang diimplementasikan dalam kelas Koleksi .

Pembungkus ini memudahkan pembuatan tampilan tersinkronisasi dari koleksi yang disediakan melalui beberapa metode pabrik statis.

Dalam tutorial ini, kita akan mendalami pembungkus sinkronisasi statis ini . Selain itu, kami akan menyoroti perbedaan antara koleksi yang disinkronkan dan koleksi serentak .

2. synchronizedCollection () Method

Wrapper sinkronisasi pertama yang akan kita bahas dalam round-up ini adalah metode synchronizedCollection () . Seperti namanya, ini mengembalikan koleksi aman thread yang didukung oleh Collection yang ditentukan .

Sekarang, untuk memahami lebih jelas bagaimana menggunakan metode ini, mari buat pengujian unit dasar:

Collection syncCollection = Collections.synchronizedCollection(new ArrayList()); Runnable listOperations = () -> { syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); }; Thread thread1 = new Thread(listOperations); Thread thread2 = new Thread(listOperations); thread1.start(); thread2.start(); thread1.join(); thread2.join(); assertThat(syncCollection.size()).isEqualTo(12); } 

Seperti yang ditunjukkan di atas, membuat tampilan tersinkronisasi dari koleksi yang disediakan dengan metode ini sangat sederhana.

Untuk mendemonstrasikan bahwa metode tersebut benar-benar mengembalikan koleksi yang aman untuk thread, pertama-tama kita membuat beberapa thread.

Setelah itu, kami kemudian memasukkan instance Runnable ke konstruktornya, dalam bentuk ekspresi lambda. Ingatlah bahwa Runnable adalah antarmuka fungsional, jadi kita bisa menggantinya dengan ekspresi lambda.

Terakhir, kami hanya memeriksa apakah setiap utas secara efektif menambahkan enam elemen ke koleksi yang disinkronkan, jadi ukuran akhirnya adalah dua belas.

3. synchronizedList () Method

Demikian pula, mirip dengan metode synchronizedCollection () , kita dapat menggunakan pembungkus synchronizedList () untuk membuat Daftar tersinkronisasi .

Seperti yang mungkin kita harapkan, metode ini mengembalikan tampilan aman-thread dari Daftar yang ditentukan :

List syncList = Collections.synchronizedList(new ArrayList());

Tidak mengherankan, penggunaan metode synchronizedList () terlihat hampir identik dengan mitra di level yang lebih tinggi, synchronizedCollection () .

Oleh karena itu, seperti yang baru saja kita lakukan di pengujian unit sebelumnya, setelah kita membuat Daftar tersinkronisasi , kita dapat menghasilkan beberapa utas. Setelah melakukan itu, kami akan menggunakannya untuk mengakses / memanipulasi Daftar target dengan cara yang aman untuk thread.

Selain itu, jika kita ingin melakukan iterasi pada koleksi yang disinkronkan dan mencegah hasil yang tidak diharapkan, kita harus secara eksplisit menyediakan implementasi loop yang aman untuk thread sendiri. Karenanya, kami dapat mencapainya menggunakan blok tersinkronisasi :

List syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c")); List uppercasedCollection = new ArrayList(); Runnable listOperations = () -> { synchronized (syncCollection) { syncCollection.forEach((e) -> { uppercasedCollection.add(e.toUpperCase()); }); } }; 

Dalam semua kasus di mana kita perlu melakukan iterasi melalui koleksi yang disinkronkan, kita harus menerapkan idiom ini. Ini karena iterasi pada koleksi tersinkronisasi dilakukan melalui beberapa panggilan ke dalam koleksi. Oleh karena itu mereka perlu dilakukan sebagai operasi atom tunggal.

Penggunaan blok tersinkronisasi memastikan atomitas operasi .

4. synchronizedMap () Method

Kelas Collections mengimplementasikan pembungkus sinkronisasi rapi lainnya, yang disebut synchronizedMap (). Kita dapat menggunakannya untuk membuat Peta tersinkronisasi dengan mudah .

Metode ini mengembalikan tampilan aman thread dari implementasi Peta yang disediakan :

Map syncMap = Collections.synchronizedMap(new HashMap()); 

5. synchronizedSortedMap () Method

Ada juga implementasi mitra dari metode synchronizedMap () . Ini disebut synchronizedSortedMap () , yang bisa kita gunakan untuk membuat instance SortedMap tersinkron :

Map syncSortedMap = Collections.synchronizedSortedMap(new TreeMap()); 

6. synchronizedSet () Method

Selanjutnya, melanjutkan ulasan ini, kami memiliki metode synchronizedSet () . Seperti namanya, ini memungkinkan kita untuk membuat Set tersinkronisasi dengan sedikit keributan.

Wrapper mengembalikan koleksi aman thread yang didukung oleh Set yang ditentukan :

Set syncSet = Collections.synchronizedSet(new HashSet()); 

7. synchronizedSortedSet () Method

Terakhir, pembungkus sinkronisasi terakhir yang akan kami tunjukkan di sini adalah synchronizedSortedSet () .

Mirip dengan implementasi wrapper lain yang telah kita ulas sejauh ini, metode ini mengembalikan versi aman untuk thread dari SortedSet yang diberikan :

SortedSet syncSortedSet = Collections.synchronizedSortedSet(new TreeSet()); 

8. Koleksi yang Disinkronkan vs Bersamaan

Sampai titik ini, kami melihat lebih dekat pada wrapper sinkronisasi framework koleksi.

Now, let's focus on the differences between synchronized collections and concurrent collections, such as ConcurrentHashMap and BlockingQueue implementations.

8.1. Synchronized Collections

Synchronized collections achieve thread-safety through intrinsic locking, and the entire collections are locked. Intrinsic locking is implemented via synchronized blocks within the wrapped collection's methods.

As we might expect, synchronized collections assure data consistency/integrity in multi-threaded environments. However, they might come with a penalty in performance, as only one single thread can access the collection at a time (a.k.a. synchronized access).

For a detailed guide on how to use synchronized methods and blocks, please check our article on the topic.

8.2. Concurrent Collections

Concurrent collections (e.g. ConcurrentHashMap), achieve thread-safety by dividing their data into segments. In a ConcurrentHashMap, for example, different threads can acquire locks on each segment, so multiple threads can access the Map at the same time (a.k.a. concurrent access).

Concurrent collections are much more performant than synchronized collections, due to the inherent advantages of concurrent thread access.

So, the choice of what type of thread-safe collection to use depends on the requirements of each use case, and it should be evaluated accordingly.

9. Kesimpulan

Dalam artikel ini, kami melihat secara mendalam kumpulan pembungkus sinkronisasi yang diterapkan dalam kelas Koleksi .

Selain itu, kami menyoroti perbedaan antara koleksi yang disinkronkan dan bersamaan, dan juga melihat pendekatan yang mereka terapkan untuk mencapai keamanan thread.

Seperti biasa, semua contoh kode yang ditampilkan dalam artikel ini tersedia di GitHub.