Panduan untuk Kata Kunci Tersinkronisasi di Java

1. Ikhtisar

Artikel singkat ini akan menjadi pengantar untuk menggunakan blok tersinkronisasi di Java.

Sederhananya, dalam lingkungan multi-utas, kondisi balapan terjadi ketika dua utas atau lebih mencoba memperbarui data bersama yang bisa berubah pada saat yang bersamaan. Java menawarkan mekanisme untuk menghindari kondisi balapan dengan menyinkronkan akses thread ke data bersama.

Sepotong logika yang ditandai dengan disinkronkan menjadi blok tersinkronisasi, yang memungkinkan hanya satu utas untuk dieksekusi pada waktu tertentu .

2. Mengapa Sinkronisasi?

Mari kita pertimbangkan kondisi balapan khas di mana kita menghitung jumlah dan beberapa utas mengeksekusi metode count () :

public class BaeldungSynchronizedMethods { private int sum = 0; public void calculate() { setSum(getSum() + 1); } // standard setters and getters } 

Dan mari kita tulis tes sederhana:

@Test public void givenMultiThread_whenNonSyncMethod() { ExecutorService service = Executors.newFixedThreadPool(3); BaeldungSynchronizedMethods summation = new BaeldungSynchronizedMethods(); IntStream.range(0, 1000) .forEach(count -> service.submit(summation::calculate)); service.awaitTermination(1000, TimeUnit.MILLISECONDS); assertEquals(1000, summation.getSum()); }

Kami hanya menggunakan ExecutorService dengan kumpulan 3 utas untuk mengeksekusi hitungan () 1000 kali.

Jika kami akan mengeksekusi ini secara serial, output yang diharapkan adalah 1000, tetapi eksekusi multi-threaded kami hampir selalu gagal dengan output aktual yang tidak konsisten misalnya:

java.lang.AssertionError: expected: but was: at org.junit.Assert.fail(Assert.java:88) at org.junit.Assert.failNotEquals(Assert.java:834) ...

Hasil ini tentu saja tidak terduga.

Cara sederhana untuk menghindari kondisi balapan adalah membuat operasi thread-safe dengan menggunakan kata kunci yang disinkronkan .

3. Kata Kunci Tersinkronisasi

Kata kunci tersinkronisasi dapat digunakan pada level yang berbeda:

  • Metode instance
  • Metode statis
  • Blok kode

Saat kami menggunakan blok tersinkronisasi , secara internal Java menggunakan monitor yang juga dikenal sebagai kunci monitor atau kunci intrinsik, untuk menyediakan sinkronisasi. Monitor ini terikat ke suatu objek, sehingga semua blok yang disinkronkan dari objek yang sama hanya dapat memiliki satu utas yang mengeksekusinya pada waktu yang sama.

3.1. Metode Instans Tersinkron

Cukup tambahkan kata kunci tersinkronisasi di deklarasi metode untuk membuat metode disinkronkan:

public synchronized void synchronisedCalculate() { setSum(getSum() + 1); }

Perhatikan bahwa setelah kita menyinkronkan metode, kasus uji lolos, dengan keluaran aktual 1000:

@Test public void givenMultiThread_whenMethodSync() { ExecutorService service = Executors.newFixedThreadPool(3); SynchronizedMethods method = new SynchronizedMethods(); IntStream.range(0, 1000) .forEach(count -> service.submit(method::synchronisedCalculate)); service.awaitTermination(1000, TimeUnit.MILLISECONDS); assertEquals(1000, method.getSum()); }

Metode instance disinkronkan melalui instance kelas yang memiliki metode tersebut. Yang berarti hanya satu utas per instance kelas yang dapat menjalankan metode ini.

3.2. Disinkronkan Stati c Metode

Metode statis disinkronkan seperti metode instance:

 public static synchronized void syncStaticCalculate() { staticSum = staticSum + 1; }

Metode ini disinkronkan pada objek Kelas yang terkait dengan kelas dan karena hanya ada satu objek Kelas per JVM per kelas, hanya satu utas yang dapat mengeksekusi di dalam metode tersinkronisasi statis per kelas, terlepas dari jumlah instance yang dimilikinya.

Mari kita uji:

@Test public void givenMultiThread_whenStaticSyncMethod() { ExecutorService service = Executors.newCachedThreadPool(); IntStream.range(0, 1000) .forEach(count -> service.submit(BaeldungSynchronizedMethods::syncStaticCalculate)); service.awaitTermination(100, TimeUnit.MILLISECONDS); assertEquals(1000, BaeldungSynchronizedMethods.staticSum); }

3.3. Blok Tersinkronisasi Dalam Metode

Terkadang kita tidak ingin menyinkronkan seluruh metode tetapi hanya beberapa instruksi di dalamnya. Ini dapat dicapai dengan menerapkan disinkronkan ke blok:

public void performSynchronisedTask() { synchronized (this) { setCount(getCount()+1); } }

Mari kita uji perubahannya:

@Test public void givenMultiThread_whenBlockSync() { ExecutorService service = Executors.newFixedThreadPool(3); BaeldungSynchronizedBlocks synchronizedBlocks = new BaeldungSynchronizedBlocks(); IntStream.range(0, 1000) .forEach(count -> service.submit(synchronizedBlocks::performSynchronisedTask)); service.awaitTermination(100, TimeUnit.MILLISECONDS); assertEquals(1000, synchronizedBlocks.getCount()); }

Perhatikan bahwa kami mengirimkan parameter ini ke blok tersinkronisasi . Ini adalah objek monitor, kode di dalam blok akan disinkronkan pada objek monitor. Sederhananya, hanya satu utas per objek monitor yang dapat dijalankan di dalam blok kode itu.

Jika metodenya statis , kami akan meneruskan nama kelas di tempat referensi objek. Dan kelas akan menjadi monitor untuk sinkronisasi blok:

public static void performStaticSyncTask(){ synchronized (SynchronisedBlocks.class) { setStaticCount(getStaticCount() + 1); } }

Mari kita uji blok di dalam metode statis :

@Test public void givenMultiThread_whenStaticSyncBlock() { ExecutorService service = Executors.newCachedThreadPool(); IntStream.range(0, 1000) .forEach(count -> service.submit(BaeldungSynchronizedBlocks::performStaticSyncTask)); service.awaitTermination(100, TimeUnit.MILLISECONDS); assertEquals(1000, BaeldungSynchronizedBlocks.getStaticCount()); }

3.4. Reentrancy

Kunci di balik metode dan blok yang disinkronkan adalah reentrant. Artinya, utas saat ini dapat memperoleh kunci tersinkronisasi yang sama berulang kali sambil menahannya:

Object lock = new Object(); synchronized (lock) { System.out.println("First time acquiring it"); synchronized (lock) { System.out.println("Entering again"); synchronized (lock) { System.out.println("And again"); } } }

Seperti yang ditunjukkan di atas, saat kita berada dalam blok tersinkronisasi , kita dapat memperoleh kunci monitor yang sama berulang kali.

4. Kesimpulan

Dalam artikel singkat ini, kami telah melihat berbagai cara menggunakan kata kunci tersinkronisasi untuk mencapai sinkronisasi utas.

Kami juga mempelajari bagaimana kondisi balapan dapat memengaruhi aplikasi kami, dan bagaimana sinkronisasi membantu kami menghindarinya. Untuk informasi selengkapnya tentang keamanan thread menggunakan kunci di Java, lihat artikel java.util.concurrent.Locks kami .

Kode lengkap untuk tutorial ini tersedia di GitHub.