Artikel ini terutama mengikuti dua artikel multi-threading sebelumnya untuk merangkum masalah keselamatan utas dalam java multi-threading.
1. Contoh keamanan utas Java yang khas
Public Class ThreadTest {public static void main (string [] args) {akun akun = akun baru ("123456", 1000); Drawmoneyrunnable drawmoneyrunnable = drawmoneyrunnable baru (akun, 700); Utas myThread1 = utas baru (drawmoneyrunnable); Thread mythread2 = utas baru (drawmoneyrunnable); myThread1.start (); mythread2.start (); }} class DrawMoneyRunnable mengimplementasikan Runnable {Private Account Account; Drawamount ganda pribadi; drawmoneyrunnable publik (akun akun, drawamount ganda) {super (); this.account = akun; this.drawamount = drawamount; } public void run () {if (Account.getBaLance ()> = drawamount) {// 1 System.out.println ("Penarikan berhasil, penarikan uang adalah:" + drawamount); Saldo Ganda = Account.GetBalance () - Drawamount; Account.setBalance (saldo); System.out.println ("Balance adalah:" + Balance); }}} class Account {private String AccountNo; Saldo ganda pribadi; akun publik () {} akun publik (String AccountNo, Saldo Ganda) {this.accountNo = AccountNo; ini. Balance = Balance; } public string getAccountNo () {return accountno; } public void setAccountNo (String AccountNo) {this.accountNo = AccountNo; } public double getBalance () {return balance; } public void setBalance (balance ganda) {this.balance = balance; }}Contoh di atas mudah dimengerti. Ada kartu bank dengan saldo 1.000. Program ini mensimulasikan adegan di mana Anda dan istri Anda menarik uang di ATM pada saat yang sama. Jalankan program ini beberapa kali dan mungkin memiliki hasil output dalam beberapa kombinasi yang berbeda. Salah satu output yang mungkin adalah:
1 Penarikan uang berhasil, penarikan uang adalah: 700.0
2 keseimbangan adalah: 300.0
3 Penarikan uang berhasil, penarikan uang adalah: 700.0
4 Saldo adalah: -400.0
Dengan kata lain, untuk kartu bank dengan saldo hanya 1.000, Anda dapat menarik total 1.400, yang jelas merupakan masalah.
Setelah analisis, masalahnya terletak pada ketidakpastian eksekusi di lingkungan multi-utas Java. CPU dapat secara acak beralih antara beberapa utas dalam keadaan siap, sehingga sangat mungkin bahwa situasi berikut terjadi: ketika Thread1 menjalankan kode pada // 1, kondisi penilaian adalah benar. Pada saat ini, CPU beralih ke Thread2, menjalankan kode di // 1, dan menemukan bahwa itu masih benar. Kemudian, Thread2 dijalankan, kemudian beralih ke Thread1, dan kemudian eksekusi selesai. Pada saat ini, hasil di atas akan muncul.
Oleh karena itu, ketika datang ke masalah keselamatan utas, itu sebenarnya berarti bahwa mengakses sumber daya bersama di lingkungan multi-utas dapat menyebabkan ketidakkonsistenan dalam sumber daya bersama ini. Oleh karena itu, untuk menghindari masalah keselamatan utas, akses bersamaan ke sumber daya bersama ini di lingkungan multi-threaded harus dihindari.
2. Metode sinkronisasi
Modifikasi kata kunci yang disinkronkan ditambahkan ke definisi metode untuk mengakses sumber daya bersama, membuat metode ini disebut metode sinkronisasi. Dapat dengan mudah dipahami bahwa metode ini terkunci, dan objek yang terkunci adalah objek itu sendiri di mana metode saat ini berada. Dalam lingkungan multi-threaded, saat menjalankan metode ini, Anda harus terlebih dahulu mendapatkan kunci sinkronisasi ini (dan paling banyak hanya satu utas dapat memperolehnya). Hanya ketika utas menjalankan metode sinkronisasi ini akan dilepaskan, dan utas lain dapat memperoleh kunci sinkronisasi ini, dan sebagainya ...
Dalam contoh di atas, sumber daya bersama adalah objek akun, dan ketika menggunakan metode sinkronisasi, ia dapat menyelesaikan masalah keamanan utas. Cukup tambahkan kata kunci sinshronized sebelum metode run ().
void run publik yang disinkronkan () {// ....}3. Sinkronisasi blok kode
Sebagaimana dianalisis di atas, menyelesaikan masalah keamanan utas hanya membutuhkan pembatasan ketidakpastian akses ke sumber daya bersama. Saat menggunakan metode sinkronisasi, seluruh badan metode menjadi keadaan eksekusi sinkron, yang dapat menyebabkan rentang sinkronisasi terjadi. Oleh karena itu, metode sinkronisasi lain - blok kode sinkronisasi - dapat diselesaikan secara langsung untuk kode yang membutuhkan sinkronisasi.
Format blok kode sinkron adalah:
disinkronkan (obj) {// ...}Di antara mereka, OBJ adalah objek kunci, jadi sangat penting untuk memilih objek mana yang akan dikunci. Secara umum, objek sumber daya bersama ini dipilih sebagai objek kunci.
Seperti pada contoh di atas, yang terbaik adalah menggunakan objek akun sebagai objek kunci. ;
4. Kunci Kunci Sinkronisasi Objek
Seperti yang dapat kita lihat di atas, justru karena kita harus sangat berhati -hati tentang pemilihan objek kunci sinkron, apakah ada solusi sederhana? Ini dapat memfasilitasi decoupling objek kunci sinkron dari sumber daya bersama, sementara juga menyelesaikan masalah keamanan utas dengan baik.
Menggunakan kunci sinkronisasi objek kunci dapat dengan mudah menyelesaikan masalah ini. Satu-satunya hal yang perlu diperhatikan adalah bahwa objek kunci perlu memiliki hubungan satu-ke-satu dengan objek sumber daya. Format umum dari kunci sinkronisasi objek kunci adalah:
Kelas X {// Tampilkan objek yang mendefinisikan kunci sinkronisasi kunci, yang memiliki hubungan satu-ke-satu dengan sumber kunci final sumber daya swasta bersama = baru reentrantlock (); public void m () {// lock lock.lock (); // ... kode yang membutuhkan sinkronisasi utas-aman // Lepaskan kunci kunci kunci.unlock (); }}5.Wait ()/notify ()/notifyAll () Thread Communication
Ketiga metode ini disebutkan dalam posting blog "Java Ringkasan Seri: Java.lang.Object". Meskipun ketiga metode ini terutama digunakan dalam multithreading, mereka sebenarnya adalah metode lokal di kelas objek. Oleh karena itu, secara teoritis, objek objek apa pun dapat digunakan sebagai nada utama dari ketiga metode ini. Dalam pemrograman multi-threading yang sebenarnya, hanya dengan menyinkronkan objek kunci untuk menyetel tiga metode ini dapat melarang komunikasi antara beberapa utas diselesaikan.
tunggu (): Menyebabkan utas saat ini menunggu dan membuatnya memasuki status pemblokiran tunggu. Sampai utas lain memanggil metode notify () atau notifyall () dari objek kunci sinkron untuk membangunkan utas.
Notify (): Bangun satu utas menunggu objek kunci sinkron ini. Jika beberapa utas sedang menunggu objek kunci sinkron ini, salah satu utas akan dipilih untuk operasi bangun. Hanya ketika utas saat ini meninggalkan kunci pada objek kunci sinkron dapat dieksekusi.
NotifyAll (): Bangun semua utas menunggu objek kunci sinkron ini. Hanya ketika utas saat ini meninggalkan kunci pada objek kunci sinkron dapat dieksekusi.
paket com.qqyumidi; kelas publik threadTest {public static void main (string [] args) {akun akun = akun baru ("123456", 0); Thread drawmoneythread = baru drawmoneythread ("get money thread", akun, 700); Thread DepositmoneyThread = New Depositmoneythread ("Simpan Utas Uang", Akun, 700); drawmoneythread.start (); depositmoneythread.start (); }} class DrawMoneyThread Extends Thread {Private Account Account; jumlah ganda pribadi; drawmoneythread publik (string threadName, akun akun, jumlah ganda) {super (threadName); this.account = akun; this.amount = jumlah; } public void run () {for (int i = 0; i <100; i ++) {account.draw (jumlah, i); }}} class depositMoneyThread memperluas utas {akun pribadi; jumlah ganda pribadi; Public DepositMoneyThread (String ThreadName, Akun Akun, Jumlah Ganda) {Super (ThreadName); this.account = akun; this.amount = jumlah; } public void run () {for (int i = 0; i <100; i ++) {Account.Deposit (jumlah, i); }}} class Account {private String AccountNo; Saldo ganda pribadi; // Identifikasi apakah sudah ada setoran di akun boolean privat bendera = false; akun publik () {} akun publik (String AccountNo, Saldo Ganda) {this.accountNo = AccountNo; ini. Balance = Balance; } public string getAccountNo () {return accountno; } public void setAccountNo (String AccountNo) {this.accountNo = AccountNo; } public double getBalance () {return balance; } public void setBalance (balance ganda) {this.balance = balance; } /** * Save money* * @param depositAmount */ public synchronized void deposit(double depositAmount, int i) { if (flag) { // Someone in the account has already saved money, and the current thread needs to wait to block try { System.out.println(Thread.currentThread().getName() + " Start to execute wait operation" + " -- i=" + i); Tunggu(); // 1 System.out.println (thread.currentThread (). GetName () + "dilakukan operasi tunggu" + " - i =" + i); } catch (InterruptedException e) {E.PrintStackTrace (); }} else {// Mulai menyimpan System.out.println (thread.currentThread (). getName () + "setoran:" + depositamount + " - i =" + i); setalance (saldo + depositamount); bendera = true; // bangun utas lain notifyall (); // 2 coba {thread.sleep (3000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println (thread.currentThread (). GetName () + "- hemat uang-- eksekusi selesai" + "- i =" + i); }} / ** * Menarik uang * * @param drawamount * / public disinkronkan void draw (drawamount ganda, int i) {if (! Flag) {// belum ada orang di akun yang telah menghemat uang, dan utas saat ini perlu menunggu untuk memblokir {System.out.println (thread.currentthread (). GetName () " +" + "Operasi" " +" + "OPERASI" " +" + " +" + "OPERASI" + " +" + " +" + "OPERASI" + " +" + " +" OPERASI " +" + " Saya); Tunggu(); System.out.println (thread.currentThread (). GetName () + "Jalankan Operasi Tunggu" + "Jalankan Operasi Tunggu" + " - i =" + i); } catch (InterruptedException e) {E.PrintStackTrace (); }} else {// Mulailah menarik sistem uang.out.println (thread.currentThread (). getName () + "Tarik uang:" + drawamount + " - i =" + i); setBalance (getbalance () - drawamount); bendera = false; // bangun utas lain notifyall (); System.out.println (thread.currentThread (). GetName () + "-penarikan uang-eksekusi selesai" + "-i =" + i); // 3}}} Contoh di atas menunjukkan penggunaan tunggu ()/notify ()/notifyAll (). Beberapa hasil output adalah:
Utas penarikan uang mulai menjalankan operasi tunggu dan menjalankan operasi tunggu-- i = 0
Menyimpan deposit utas: 700.0 - i = 0
Hemat Uang Utas Save-Ekseksi-I = 0
Thread Penghematan Uang perlu melakukan operasi tunggu-- i = 1
Utas penarikan uang mengeksekusi operasi tunggu dan operasi tunggu-- i = 0
Tarik Uang Uang Tarik Uang: 700.0 - I = 1
Uang Penarikan Uang-THRAWAL-Execution-I = 1
Utas untuk menarik uang harus mulai menjalankan operasi tunggu dan menjalankan operasi tunggu-- i = 2
Benang Penghematan Uang mengeksekusi operasi tunggu-- i = 1
Simpan setoran utas: 700.0 - i = 2
Hemat Uang Utas Save-Ekseksi-I = 2
Utas penarikan menjalankan operasi tunggu dan menjalankan operasi tunggu-- i = 2
Menarik utas uang menarik uang: 700.0 - i = 3
Uang Penarikan Uang-THRAWAL-EXECUTION-I = 3
Utas untuk menarik uang harus menjalankan operasi tunggu dan menjalankan operasi tunggu-- i = 4
Simpan setoran utas: 700.0 - i = 3
Hemat Uang Utas Save-Ekseksi-I = 3
Thread Penghematan Uang Perlu Melakukan Operasi Tunggu-- I = 4
Utas penarikan uang mengeksekusi operasi tunggu dan operasi tunggu-- i = 4
Menarik utas uang menarik uang: 700.0 - i = 5
Uang Penarikan Uang-THRAWAL-Execution-I = 5
Utas untuk menarik uang harus mulai melakukan operasi tunggu dan menjalankan operasi tunggu-- i = 6
Benang Penghematan Uang mengeksekusi operasi tunggu-- i = 4
Simpan setoran utas: 700.0 - i = 5
Hemat Uang Utas Save-Ekseksi-I = 5
Thread Penghematan Uang Perlu Melakukan Operasi Tunggu-- I = 6
Utas penarikan uang mengeksekusi operasi tunggu dan operasi tunggu-- i = 6
Menarik utas uang menarik uang: 700.0 - i = 7
Uang Penarikan Uang-THRAWAL-Execution-I = 7
Utas penarikan uang mulai menjalankan operasi tunggu dan menjalankan operasi tunggu-I = 8
Benang Penghematan Uang mengeksekusi operasi tunggu-- i = 6
Simpan setoran utas: 700.0 - i = 7
Karena itu, kita perlu memperhatikan poin -poin berikut:
1. Setelah metode tunggu () dieksekusi, utas saat ini segera memasuki status pemblokiran tunggu, dan kode selanjutnya tidak akan dieksekusi;
2. Setelah metode notify ()/notifyAll () dijalankan, objek utas (any-notify ()/all-notifyall ()) pada objek kunci sinkronisasi ini akan dibangunkan. Namun, objek kunci sinkronisasi tidak dirilis saat ini. Dengan kata lain, jika ada kode di balik notify ()/notifyall (), itu akan terus dilanjutkan. Hanya ketika utas saat ini dieksekusi, objek kunci sinkronisasi akan dirilis;
3. Setelah notify ()/notifyAll () dieksekusi, jika ada metode tidur () di sebelah kanan, utas saat ini akan memasuki keadaan pemblokiran, tetapi kunci objek sinkronisasi tidak dilepaskan dan masih dipertahankan dengan sendirinya. Kemudian utas akan terus dieksekusi setelah periode waktu tertentu, 2 berikutnya;
4. tunggu ()/notify ()/nitifyall () melengkapi komunikasi atau kolaborasi antara utas berdasarkan kunci objek yang berbeda. Oleh karena itu, jika itu adalah kunci objek sinkronisasi yang berbeda, ia akan kehilangan artinya. Pada saat yang sama, kunci objek sinkronisasi adalah yang terbaik untuk mempertahankan korespondensi satu-ke-satu dengan objek sumber daya bersama;
5. Ketika utas tunggu bangun dan mengeksekusi, kode metode tunggu () yang dieksekusi terakhir kali terus dieksekusi.
Tentu saja, contoh di atas relatif sederhana, hanya untuk hanya menggunakan metode tunggu ()/notify ()/noitifyAll (), tetapi pada dasarnya, itu sudah merupakan model konsumen produsen sederhana.
Serangkaian artikel:
Penjelasan Java Multi-Threaded Instances (I)
Penjelasan terperinci tentang Java Multi-Threaded Instances (II)
Penjelasan terperinci tentang Java Multi-Threaded Instances (III)