1. Kapan masalah keamanan utas akan terjadi?
Tidak akan ada masalah keamanan utas dalam satu utas, tetapi dalam pemrograman multi-threaded, dimungkinkan untuk mengakses sumber daya yang sama secara bersamaan. Sumber daya ini dapat berupa berbagai jenis sumber daya: variabel, objek, file, tabel database, dll. Ketika beberapa utas mengakses sumber daya yang sama pada saat yang sama, akan ada masalah:
Karena proses yang dieksekusi oleh masing -masing utas tidak terkendali, ada kemungkinan bahwa hasil akhir akan bertentangan dengan keinginan yang sebenarnya atau akan secara langsung menyebabkan kesalahan program.
Mari berikan contoh sederhana:
Sekarang ada dua utas yang membaca data dari jaringan secara terpisah dan kemudian memasukkannya ke dalam tabel database, yang mensyaratkan bahwa data duplikat tidak dapat dimasukkan.
Maka harus ada dua operasi dalam proses memasukkan data:
1) Periksa apakah ada data dalam database;
2) jika ada, itu tidak akan dimasukkan; Jika tidak ada, itu akan dimasukkan ke dalam database.
Jika kedua utas diwakili oleh Thread-1 dan Thread-2 masing-masing, dan pada beberapa titik, Thread-1 dan Thread-2 keduanya membaca data X, ini mungkin terjadi:
Thread-1 memeriksa apakah data X ada dalam database, dan Thread-2 juga memeriksa apakah data X ada dalam database.
Akibatnya, hasil dari pemeriksaan kedua utas adalah bahwa data x tidak ada dalam database, sehingga kedua utas memasukkan data X ke dalam tabel database masing -masing.
Ini adalah masalah keamanan utas, yaitu, ketika banyak utas mengakses sumber daya pada saat yang sama, hasil menjalankan program bukan hasil yang ingin Anda lihat.
Di sini, sumber ini disebut: sumber daya kritis (juga dikenal sebagai sumber daya bersama).
Artinya, ketika beberapa utas mengakses sumber daya kritis (satu objek, atribut dalam suatu objek, file, database, dll.), Masalah keselamatan utas mungkin muncul.
Namun, ketika beberapa utas menjalankan metode, variabel lokal di dalam metode ini bukan sumber daya yang kritis, karena metode ini dieksekusi pada tumpukan, sedangkan tumpukan java adalah thread-private, sehingga tidak akan ada masalah keselamatan utas.
2. Bagaimana menyelesaikan masalah keamanan utas?
Jadi secara umum, bagaimana menyelesaikan masalah keamanan utas?
Pada dasarnya, ketika memecahkan masalah keamanan utas, semua mode konkurensi mengadopsi solusi "akses serial ke sumber daya kritis", yaitu, pada saat yang sama, hanya satu utas yang dapat mengakses sumber daya kritis, juga dikenal sebagai akses bersama yang sinkron.
Secara umum, kunci ditambahkan sebelum kode yang mengakses sumber daya kritis. Setelah mengakses sumber daya kritis, kunci dilepaskan dan utas lainnya terus mengakses.
Di Java, dua cara disediakan untuk mengimplementasikan akses mutex sinkron: disinkronkan dan kunci.
Artikel ini terutama berbicara tentang penggunaan sinkronisasi, dan penggunaan kunci dijelaskan dalam posting blog berikutnya.
Tiga. Metode Sinkronisasi Sinkronisasi atau Blok Sinkronisasi
Sebelum memahami penggunaan kata kunci yang disinkronkan, mari kita lihat konsep: Mutex Locks, seperti namanya: kunci yang dapat mencapai tujuan akses mutex.
Untuk memberikan contoh sederhana: Jika sumber kritis ditambahkan ke mutex, ketika satu utas mengakses sumber daya kritis, utas lainnya hanya bisa menunggu.
Di Java, setiap objek memiliki tanda kunci (monitor), juga dikenal sebagai monitor. Ketika beberapa utas mengakses objek secara bersamaan, utas hanya dapat mengaksesnya jika memperoleh kunci objek.
Di Java, kata kunci yang disinkronkan dapat digunakan untuk menandai metode atau blok kode. Ketika utas memanggil metode yang disinkronkan objek atau mengakses blok kode yang disinkronkan, utas memperoleh kunci objek. Utas lain tidak dapat mengakses metode untuk saat ini. Hanya ketika metode dijalankan atau blok kode dieksekusi, utas akan melepaskan kunci objek, dan utas lainnya dapat menjalankan metode atau blok kode.
Berikut ini adalah beberapa contoh sederhana untuk menggambarkan penggunaan kata kunci yang disinkronkan:
1. Metode yang disinkronkan
Dalam kode berikut, dua utas memanggil objek InsertData untuk memasukkan data:
tes kelas publik {public static void main (string [] args) {final insertData insertData = new INSertData (); utas baru () {public void run () {insertData.insert (thread.currentThread ()); }; }.awal(); utas baru () {public void run () {insertData.insert (thread.currentThread ()); }; }.awal(); }} kelas insertData {private arrayList <Integer> arrayList = arraylist baru <Integer> (); public void insert (utas utas) {untuk (int i = 0; i <5; i ++) {System.out.println (thread.getName ()+"Masukkan data"+i); arraylist.add (i); }}} Saat ini, hasil output dari program ini adalah:
Ini menunjukkan bahwa dua utas menjalankan metode insert secara bersamaan.
Jika kata kunci yang disinkronkan ditambahkan sebelum metode masukkan, hasil jalankan adalah:
kelas insertData {private arrayList <Integer> arrayList = new ArrayList <Integer> (); Sinkronisasi public void Insert (thread thread) {for (int i = 0; i <5; i ++) {System.out.println (thread.getName ()+"masukkan data"+i); arraylist.add (i); }}}Dari output di atas, data yang dimasukkan dalam Thread-1 hanya dilakukan setelah Thread-0 dimasukkan. Ini menunjukkan bahwa thread-0 dan thread-1 mengeksekusi metode insert secara berurutan.
Ini adalah metode yang disinkronkan.
Namun, ada beberapa poin yang perlu diperhatikan:
1) Ketika suatu utas mengakses metode yang disinkronkan dari suatu objek, utas lain tidak dapat mengakses metode objek yang disinkronkan lainnya. Alasan ini sangat sederhana, karena suatu objek hanya memiliki satu kunci. Ketika utas memperoleh kunci objek, utas lain tidak dapat memperoleh kunci objek, sehingga mereka tidak dapat mengakses metode objek yang disinkronkan lainnya.
2) Saat utas mengakses metode objek yang disinkronkan, maka utas lain dapat mengakses metode objek yang tidak disinkronkan. Alasan untuk ini sangat sederhana. Mengakses metode yang tidak disinkronkan tidak memerlukan kunci objek. Jika suatu metode tidak dimodifikasi dengan kata kunci yang disinkronkan, itu berarti tidak akan menggunakan sumber daya kritis, maka utas lain dapat mengakses metode ini.
3) Jika utas A perlu mengakses metode yang disinkronkan Fun1 dari objek objek1, dan utas lain B perlu mengakses metode yang disinkronkan Fun1 dari objek objek2, bahkan jika Object1 dan Object2 adalah tipe yang sama), tidak akan ada masalah keamanan utas, karena mereka mengakses objek yang berbeda, sehingga tidak ada masalah pengecualian bersama.
2. Blok kode yang disinkronkan
Blok kode yang disinkronkan mirip dengan formulir berikut:
disinkronkan (syncoBject) {
}
Ketika blok kode ini dieksekusi dalam utas, utas memperoleh kunci objek synobject, sehingga mustahil bagi utas lain untuk mengakses blok kode secara bersamaan.
Synobject dapat berupa ini, mewakili kunci yang memperoleh objek saat ini, atau dapat menjadi atribut di kelas, mewakili kunci yang memperoleh atribut.
Misalnya, metode insert di atas dapat diubah ke dua bentuk berikut:
kelas insertData {private arrayList <Integer> arrayList = new ArrayList <Integer> (); public void insert (thread thread) {disinkronkan (this) {for (int i = 0; i <100; i ++) {System.out.println (thread.getName ()+"masukkan data"+i); arraylist.add (i); }}}} class InsertData {private arrayList <Integer> arrayList = ArrayList baru <Integer> (); objek objek pribadi = objek baru (); public void insert (thread thread) {disinkronkan (objek) {for (int i = 0; i <100; i ++) {System.out.println (thread.getName ()+"masukkan data"+i); arraylist.add (i); }}}}Seperti yang dapat dilihat dari hal di atas, blok kode yang disinkronkan jauh lebih fleksibel untuk digunakan daripada metode yang disinkronkan. Karena mungkin hanya sebagian dari kode dalam suatu metode yang perlu disinkronkan, jika seluruh metode disinkronkan saat ini, itu akan mempengaruhi efisiensi eksekusi program. Masalah ini dapat dihindari dengan menggunakan blok kode yang disinkronkan. Blok kode yang disinkronkan hanya dapat menyinkronkan di mana sinkronisasi diperlukan.
Selain itu, setiap kelas juga memiliki kunci, yang dapat digunakan untuk mengontrol akses bersamaan ke anggota data statis.
Dan jika utas mengeksekusi metode sinkronisasi non-statis suatu objek, dan utas lain perlu menjalankan metode tersinkronisasi statis dari kelas yang dimiliki oleh objek, tidak akan ada pengucilan timbal balik pada saat ini, karena mengakses metode sinkronisasi statis menempati kunci kelas, sementara mengakses metode sinkronisasi non-statis menempati kunci objek, demikian ada di sana.
Anda akan mengerti dengan melihat kode berikut:
tes kelas publik {public static void main (string [] args) {final insertData insertData = new INSertData (); utas baru () {@Override public void run () {insertData.insert (); } }.awal(); utas baru () {@Override public void run () {insertData.insert1 (); } }.awal(); }} kelas insertData {public disinkronkan void insert () {System.out.println ("Execute Insert"); coba {thread.sleep (5000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Execute Insert1"); } public static static void insert1 () {System.out.println ("Execute Insert1"); System.out.println ("Execute Insert1"); }} Hasil eksekusi;
Metode insert dijalankan di utas pertama, yang tidak akan menyebabkan utas kedua memblokir metode insert1.
Mari kita lihat apa yang dilakukan kata kunci yang disinkronkan. Mari kita mendekompilasi bytecode -nya. Kode bytecompiled dari kode berikut adalah:
Public Class InsertData {Private Object objek = objek baru (); public void insert (utas utas) {disinkronkan (objek) {}} public disinkronkan void insert1 (thread thread) {} public void insert2 (thread thread) {}}Dari bytecode yang diperoleh dengan mendekompilasi, dapat dilihat bahwa blok kode yang disinkronkan sebenarnya memiliki dua instruksi: Monitorenter dan Monitorexit. Ketika instruksi Monitorenter dieksekusi, jumlah kunci objek akan ditingkatkan sebesar 1, sedangkan ketika instruksi monitorexit dieksekusi, jumlah kunci objek akan dikurangi sebesar 1. Bahkan, ini sangat mirip dengan operasi PV dalam sistem operasi. Operasi PV dalam sistem operasi digunakan untuk mengontrol beberapa utas untuk mengakses sumber daya kritis. Untuk metode yang disinkronkan, utas dalam eksekusi mengenali apakah struktur Method_Info dari metode ini memiliki pengaturan bendera ACC_SYNCHRONIZed, dan kemudian secara otomatis memperoleh kunci objek, memanggil metode tersebut, dan akhirnya melepaskan kunci. Jika pengecualian terjadi, utas secara otomatis melepaskan kunci.
Satu hal yang perlu diperhatikan: Untuk metode yang disinkronkan atau blok kode yang disinkronkan, ketika pengecualian terjadi, JVM akan secara otomatis melepaskan kunci yang ditempati oleh utas saat ini, sehingga tidak akan ada kebuntuan karena pengecualian.
3. Beberapa hal penting lainnya tentang disinkronkan
1. Perbedaan antara disinkronkan yang disinkronkan dan statis
Kunci yang disinkronkan, instance kelas saat ini untuk mencegah utas lain mengakses semua blok yang disinkronkan dari instance kelas secara bersamaan. Perhatikan bahwa ini adalah "contoh saat ini dari kelas", dan tidak ada kendala seperti itu pada dua contoh kelas yang berbeda. Kemudian disinkronkan statis terjadi untuk mengontrol akses ke semua contoh kelas. Statis yang disinkronkan membatasi utas untuk mengakses semua contoh kelas di JVM pada saat yang sama dan mengakses kode yang sesuai dengan cepat. Faktanya, jika ada disinkronkan dalam metode atau blok kode di kelas, maka setelah menghasilkan instance dari kelas ini, kelas akan memiliki pemantauan cepat, dan utas akses bersamaan ke instance yang dimodifikasi yang disinkronkan dilindungi dengan cepat. Sinkronisasi statis adalah publik salah satu pemantauan, yang merupakan perbedaan antara keduanya. Artinya, disinkronkan setara dengan ini. Sintrum, dan disinkronkan statis setara dengan sesuatu.
Seorang penulis Jepang, "pola desain multithreaded Java" Java Chenghao memiliki kolom seperti ini:
PULBIC CLASS SESUATU () {public disinkronkan void issynca () {} public disinkronkan void issyncb () {} public static static void csynca () {} public static void csyncb () {}} public static static void csyncb () {} Kemudian, jika dua contoh A dan B dari kelas sesuatu ditambahkan, mengapa kelompok metode berikut dapat diakses secara bersamaan dengan lebih dari satu utas? Axissynca () dan x.issyncb ()
bxissynca () dan y.issynca ()
cxcsynca () dan y.csyncb ()
dxissynca () dan sesuatu.csynca ()
Di sini, jelas bahwa itu dapat dinilai:
A, keduanya akses ke domain yang disinkronkan dari instance yang sama, sehingga tidak dapat diakses secara bersamaan. B adalah untuk contoh yang berbeda, sehingga dapat diakses secara bersamaan. Karena disinkronkan statis, contoh yang berbeda masih akan dibatasi, yang setara dengan sesuatu.
Jadi, bagaimana dengan D?, Jawaban dalam buku ini dapat diakses secara bersamaan. Alasan jawabannya adalah bahwa sinkronisasi adalah bahwa metode instan dan metode kelas yang disinkronkan berbeda dari kunci.
Analisis pribadi berarti bahwa disinkronkan yang disinkronkan dan statis setara dengan dua geng, yang masing -masing memiliki kendali sendiri, dan tidak ada kendala satu sama lain dan dapat diakses pada saat yang sama. Tidak jelas bagaimana sinkronisasi diimplementasikan dalam desain internal Java.
Kesimpulan: A: Statis yang disinkronkan adalah ruang lingkup kelas tertentu. Csync statis yang disinkronkan {} mencegah beberapa utas mengakses metode statis yang disinkronkan di kelas ini secara bersamaan. Ini bekerja pada semua instance objek kelas.
B: Sinkronisasi adalah ruang lingkup sebuah instance. Sinkronisasi ISSYNC () {} mencegah beberapa utas mengakses metode yang disinkronkan dalam contoh ini secara bersamaan.
2. Perbedaan antara metode yang disinkronkan dan kode yang disinkronkan dengan cepat
Tidak ada perbedaan antara metode yang disinkronkan () {} dan disinkronkan (ini) {}, tetapi metode yang disinkronkan () {} lebih mudah untuk pemahaman membaca, sementara disinkronkan (ini) {} dapat secara lebih akurat mengontrol konflik membatasi area akses, dan kadang -kadang berkinerja lebih efisien.
3. Kata kunci yang disinkronkan tidak dapat diwariskan