0. Kode Pertanyaan Perintis
Kode berikut menunjukkan penghitung di mana dua utas menumpuk I pada saat yang sama, masing -masing melakukan 1000.000 kali. Hasil yang kami harapkan jelas saya = 2000000. Namun, setelah kami menjalankannya berkali -kali, kami akan menemukan bahwa nilai saya akan selalu kurang dari tahun 2000000. Ini karena ketika dua utas menulis saya pada saat yang sama, hasil dari salah satu utas akan menimpa yang lain.
Akuntansi Kelas Publik mengimplementasikan runnable {static int i = 0; public void peningkatan () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {peningkatan (); }} public static void main (string [] args) melempar interruptedException {Accountingsync Accountingsync = New AccountingSync (); Utas T1 = utas baru (AccountingSync); Thread t2 = utas baru (AccountingSync); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} Untuk secara fundamental menyelesaikan masalah ini, kita harus memastikan bahwa beberapa utas harus sepenuhnya disinkronkan saat beroperasi i. Dengan kata lain, ketika utas A menulis saya, utas B tidak hanya tidak bisa menulis, tetapi juga tidak dapat membacanya.
1. Peran kata kunci yang disinkronkan
Fungsi kata kunci yang disinkronkan sebenarnya adalah untuk mewujudkan sinkronisasi antar utas. Tugasnya adalah mengunci kode yang disinkronkan, sehingga hanya satu utas yang dapat memasukkan blok sinkronisasi sekaligus, sehingga memastikan keamanan antar utas. Sama seperti pada kode di atas, operasi I ++ hanya dapat dieksekusi oleh utas lain secara bersamaan.
2. Penggunaan kata kunci yang disinkronkan
Tentukan kunci objek: Kunci objek yang diberikan, masukkan blok kode sinkronisasi untuk mendapatkan kunci objek yang diberikan
Langsung bertindak pada metode instan: setara dengan mengunci instance saat ini. Memasukkan blok kode sinkron, Anda harus mendapatkan kunci instance saat ini (ini mensyaratkan bahwa saat membuat utas, Anda harus menggunakan instance runnable yang sama)
Bertindak langsung pada metode statis: setara dengan mengunci kelas saat ini. Sebelum memasukkan blok kode sinkron, Anda harus mendapatkan kunci kelas saat ini.
2.1 Tentukan objek untuk mengunci
Kode berikut berlaku disinkronkan ke objek yang diberikan. Ada catatan di sini bahwa objek yang diberikan harus statis, jika tidak, kami tidak akan berbagi objek satu sama lain setiap kali kami menjadi utas baru, sehingga arti penguncian tidak akan ada lagi.
Akuntansi Kelas Publik mengimplementasikan Runnable {Final Static Object Object = Objek baru (); statis int i = 0; public void peningkatan () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {disinkronkan (objek) {peningkatan (); }}} public static void main (string [] args) melempar interruptedException {thread t1 = utas baru (AccountingSync ()); Thread t2 = utas baru (AccountingSync ()); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} 2.2 Langsung bertindak pada metode instance
Kata kunci yang disinkronkan bertindak pada metode instan, yaitu, sebelum memasukkan metode peningkatan (), utas harus mendapatkan kunci instance saat ini. Ini mengharuskan kita untuk menggunakan instance objek Runnable yang sama saat membuat instance utas. Kalau tidak, kunci utas tidak pada contoh yang sama, jadi tidak ada cara untuk berbicara tentang masalah penguncian/sinkronisasi.
Akuntansi Kelas Publik mengimplementasikan runnable {static int i = 0; void disinkronkan publik meningkat () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {peningkatan (); }} public static void main (string [] args) melempar interruptedException {Accountingsync Accountingsync = New AccountingSync (); Utas T1 = utas baru (AccountingSync); Thread t2 = utas baru (AccountingSync); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} Harap perhatikan tiga baris pertama dari metode utama untuk menggambarkan penggunaan kata kunci yang benar pada metode instan.
2.3 Bertindak Langsung pada Metode Statis
Untuk menerapkan kata kunci yang disinkronkan ke metode statis, tidak perlu menggunakan dua utas untuk menunjuk ke metode runnable yang sama seperti pada contoh di atas. Karena blok metode perlu meminta kunci kelas saat ini, bukan instance saat ini, utas masih dapat disinkronkan dengan benar.
Akuntansi Kelas Publik mengimplementasikan runnable {static int i = 0; void sinkronisasi statis publik meningkat () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {peningkatan (); }} public static void main (string [] args) melempar interruptedException {thread t1 = utas baru (AccountingsYncync ()); Thread t2 = utas baru (AccountingSync ()); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }}3. Penguncian yang salah
Dari contoh di atas, kita tahu bahwa jika kita membutuhkan aplikasi penghitung, untuk memastikan kebenaran data, kita secara alami perlu mengunci penghitung, jadi kita dapat menulis kode berikut:
kelas publik badlockoninteger mengimplementasikan runnable {static integer i = 0; @Override public void run () {for (int j = 0; j <1000000; j ++) {disinkronkan (i) {i ++; }}} public static void main (string [] args) melempar interruptedException {badlockoninteger badlockoninteger = new badlockoninteger (); Utas T1 = utas baru (badlockoninteger); Utas T2 = utas baru (badlockoninteger); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }}Ketika kami menjalankan kode di atas, kami akan menemukan bahwa output saya sangat kecil. Ini berarti utasnya tidak aman.
Untuk menjelaskan masalah ini, kita perlu mulai dengan Integer: Di Java, Integer adalah objek invarian. Seperti string, setelah objek dibuat, itu tidak dapat dimodifikasi. Jika Anda memiliki bilangan bulat = 1, maka itu akan selalu menjadi 1. Bagaimana jika Anda menginginkan objek ini = 2? Anda hanya dapat menciptakan kembali bilangan bulat. Setelah masing -masing I ++, setara dengan memanggil nilai metode integer. Mari kita lihat kode sumber nilai Metode Integer:
Nilai integer statis publik (int i) {if (i> = integercache.low && i <= integercache.high) mengembalikan integercache.cache [i + (-integercercache.low)]; mengembalikan bilangan bulat baru (i);} Integer.valueof () sebenarnya adalah metode pabrik, yang cenderung mengembalikan objek integer baru dan menyalin nilai ke i;
Karena itu, kita tahu alasan masalahnya. Karena di antara beberapa utas, karena i ++ datang setelah saya, saya menunjuk ke objek baru, utas dapat memuat instance objek yang berbeda setiap kali terkunci. Solusinya sangat sederhana. Anda dapat menyelesaikannya dengan menggunakan salah satu dari tiga metode sinkronisasi di atas.