1. Gambaran keseluruhan
Artikel ringkas ini akan menjadi intro untuk menggunakan blok yang disegerakkan di Java.
Ringkasnya, dalam persekitaran multi-utas, keadaan perlumbaan berlaku apabila dua atau lebih utas cuba mengemas kini data bersama yang dapat berubah pada masa yang sama. Java menawarkan mekanisme untuk mengelakkan keadaan perlumbaan dengan menyegerakkan akses utas ke data bersama.
Sepotong logik yang ditandai dengan diselaraskan menjadi blok yang disegerakkan, yang membolehkan hanya satu utas untuk dilaksanakan pada waktu tertentu .
2. Mengapa Penyegerakan?
Mari pertimbangkan keadaan perlumbaan biasa di mana kita mengira jumlah dan beberapa utas menjalankan kaedah hitung () :
public class BaeldungSynchronizedMethods { private int sum = 0; public void calculate() { setSum(getSum() + 1); } // standard setters and getters }
Dan mari kita tulis ujian mudah:
@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 melaksanakan pengiraan () 1000 kali.
Sekiranya kita melaksanakan ini secara bersiri, output yang diharapkan adalah 1000, tetapi pelaksanaan multi-utas kita gagal hampir setiap kali dengan output sebenar yang tidak konsisten seperti:
java.lang.AssertionError: expected: but was: at org.junit.Assert.fail(Assert.java:88) at org.junit.Assert.failNotEquals(Assert.java:834) ...
Hasil ini tentunya tidak dijangka.
Cara mudah untuk mengelakkan keadaan perlumbaan adalah menjadikan operasi benang selamat dengan menggunakan kata kunci yang disegerakkan .
3. Kata Kunci Yang Disegerakkan
Kata kunci yang diselaraskan boleh digunakan pada tahap yang berbeza:
- Kaedah contoh
- Kaedah statik
- Sekatan kod
Ketika kita menggunakan blok yang disinkronkan , secara internal Java menggunakan monitor yang juga dikenal sebagai kunci monitor atau kunci intrinsik, untuk memberikan penyegerakan. Monitor ini terikat pada objek, oleh itu semua blok yang disegerakkan dari objek yang sama hanya boleh mempunyai satu utas yang melaksanakannya pada masa yang sama.
3.1. Disegerakkan Kaedah instance
Cukup tambahkan kata kunci yang disegerakkan dalam deklarasi kaedah untuk menjadikan kaedah disegerakkan:
public synchronized void synchronisedCalculate() { setSum(getSum() + 1); }
Perhatikan bahawa setelah kita menyegerakkan kaedah, kes ujian akan berlalu, dengan output sebenar 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()); }
Kaedah contoh diselaraskan dengan contoh kelas yang memiliki kaedah tersebut. Yang bermaksud hanya satu utas setiap contoh kelas yang dapat melaksanakan kaedah ini.
3.2. Kaedah Stati c Segerak
Kaedah statik diselaraskan seperti kaedah contoh:
public static synchronized void syncStaticCalculate() { staticSum = staticSum + 1; }
Kaedah ini diselaraskan pada objek Kelas yang berkaitan dengan kelas dan kerana hanya ada satu objek Kelas yang ada per JVM setiap kelas, hanya satu utas yang dapat dilaksanakan di dalam kaedah penyegerakan statik per kelas, tanpa mengira jumlah kejadian yang dimilikinya.
Mari mengujinya:
@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 Disegerakkan Dalam Kaedah
Kadang-kadang kita tidak mahu menyelaraskan keseluruhan kaedah tetapi hanya beberapa arahan di dalamnya. Ini dapat dicapai dengan menerapkan penyegerakan 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 bahawa kami meneruskan parameter ini ke blok yang disegerakkan . Ini adalah objek monitor, kod di dalam blok disegerakkan pada objek monitor. Ringkasnya, hanya satu utas per objek monitor yang dapat dilaksanakan di dalam blok kod tersebut.
Sekiranya kaedahnya statik , kita akan meneruskan nama kelas di tempat rujukan objek. Dan kelas akan menjadi monitor penyegerakan blok:
public static void performStaticSyncTask(){ synchronized (SynchronisedBlocks.class) { setStaticCount(getStaticCount() + 1); } }
Mari kita uji blok di dalam kaedah statik :
@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. Kemasukan semula
Kunci di sebalik kaedah dan blok yang disegerakkan masuk semula. Maksudnya, utas semasa dapat memperoleh kunci disegerakkan yang sama berulang kali sambil memegangnya:
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, semasa kita berada di blok yang disegerakkan , kita dapat memperoleh kunci monitor yang sama berulang kali.
4. Kesimpulan
Dalam artikel ringkas ini, kami telah melihat pelbagai cara menggunakan kata kunci yang disegerakkan untuk mencapai penyegerakan utas.
Kami juga meneroka bagaimana keadaan perlumbaan dapat mempengaruhi aplikasi kami, dan bagaimana penyegerakan membantu kami mengelakkannya. Untuk lebih lanjut mengenai keselamatan benang menggunakan kunci di Java, rujuk artikel java.util.concurrent.Locks kami .
Kod lengkap untuk tutorial ini boleh didapati di GitHub.