Dalam pengembangan umum, penulis sering melihat bahwa banyak siswa hanya menggunakan beberapa metode dasar dalam memperlakukan model pengembangan bersamaan Java. Misalnya, volatile, disinkronkan. Paket bersamaan canggih seperti Lock dan Atomic tidak sering digunakan oleh banyak orang. Saya pikir sebagian besar alasannya adalah karena kurangnya atribut pada prinsip. Dalam pekerjaan pengembangan yang sibuk, siapa yang dapat secara akurat memahami dan menggunakan model konkurensi yang benar?
Jadi baru -baru ini, berdasarkan ide ini, saya berencana untuk mengatur mekanisme kontrol konkurensi menjadi sebuah artikel. Ini bukan hanya memori pengetahuan Anda sendiri, tetapi juga berharap bahwa konten yang disebutkan dalam artikel ini dapat membantu sebagian besar pengembang.
Pengembangan program paralel pasti melibatkan masalah seperti multi-threading dan kolaborasi multi-tugas dan berbagi data. Di JDK, beberapa cara disediakan untuk mengimplementasikan kontrol bersamaan antara beberapa utas. Misalnya, umum digunakan: kunci internal, kunci masuk kembali, kunci baca-tulis dan semaphore.
Model memori Java
Di Java, setiap utas memiliki area memori yang berfungsi, yang menyimpan salinan nilai variabel dalam memori utama yang dibagikan oleh semua utas. Ketika sebuah utas dijalankan, ia mengoperasikan variabel -variabel ini dalam memori kerjanya sendiri.
Untuk mengakses variabel bersama, utas biasanya memperoleh kunci dan menghapus area memori kerjanya, yang memastikan bahwa variabel bersama dimuat dengan benar dari area memori bersama dari semua utas ke dalam area memori kerja utas. Ketika utas membuka kunci, nilai variabel di area memori kerja dijamin akan dikaitkan dengan memori bersama.
Ketika utas menggunakan variabel tertentu, terlepas dari apakah program menggunakan operasi sinkronisasi utas dengan benar, nilai yang diperolehnya harus bernilai yang disimpan dalam variabel dengan sendirinya atau utas lainnya. Misalnya, jika dua utas menyimpan nilai atau referensi objek yang berbeda ke dalam variabel bersama yang sama, maka nilai variabel berasal dari utas ini atau dari utas tersebut, dan nilai variabel bersama tidak akan terdiri dari nilai referensi dari kedua utas tersebut.
Alamat yang dapat diakses oleh program Java saat variabel digunakan. Ini tidak hanya termasuk variabel tipe dasar dan variabel tipe referensi, tetapi juga variabel tipe array. Variabel yang disimpan di area memori utama dapat dibagikan oleh semua utas, tetapi tidak mungkin bagi satu utas untuk mengakses parameter atau variabel lokal dari utas lain, sehingga pengembang tidak perlu khawatir tentang masalah keselamatan utas dari variabel lokal.
Variabel volatil dapat dilihat di antara beberapa utas
Karena setiap utas memiliki area memori kerjanya sendiri, mungkin tidak terlihat oleh utas lain ketika satu utas mengubah data memori kerjanya sendiri. Untuk melakukan ini, Anda dapat menggunakan kata kunci yang mudah menguap untuk memecahkan semua utas untuk membaca dan menulis variabel dalam memori, sehingga variabel volatil terlihat di antara banyak utas.
Variabel dinyatakan sebagai volatil dapat dijamin sebagai berikut:
1. Modifikasi variabel oleh utas lain dapat segera tercermin dalam utas saat ini;
2. Pastikan bahwa modifikasi utas saat ini dari variabel volatil dapat ditulis kembali ke memori bersama dalam waktu dan dilihat oleh utas lain;
3. Gunakan variabel yang dinyatakan secara volatile, dan kompiler akan memastikan keteraturan mereka.
Kata kunci yang disinkronkan
Kata kunci yang disinkronkan disinkronkan adalah salah satu metode sinkronisasi yang paling umum digunakan dalam bahasa Java. Dalam versi JDK awal, kinerja yang disinkronkan tidak terlalu baik, dan nilainya cocok untuk kesempatan di mana kompetisi kunci tidak terlalu sengit. Di JDK6, kesenjangan antara kunci yang disinkronkan dan tidak adil telah menyempit. Lebih penting lagi, disinkronkan lebih ringkas dan jelas, dan kodenya dapat dibaca dan dipelihara.
Metode untuk mengunci objek:
Metode void yang disinkronkan publik () {}
Ketika metode metode () dipanggil, utas panggilan harus terlebih dahulu mendapatkan objek saat ini. Jika kunci objek saat ini dipegang oleh utas lain, utas panggilan akan menunggu. Setelah pelanggaran selesai, kunci objek akan dirilis. Metode di atas setara dengan metode penulisan berikut:
Metode public void () {disinkronkan (ini) {// lakukan sesuatu ...}} Kedua, disinkronkan juga dapat digunakan untuk membangun blok sinkronisasi. Dibandingkan dengan metode sinkronisasi, blok sinkronisasi dapat mengontrol rentang kode sinkronisasi lebih akurat. Kode sinkronisasi kecil sangat cepat masuk dan keluar dari kunci, sehingga memberikan sistem throughput yang lebih tinggi.
Metode public void (objek o) {// beforesynchronized (o) {// lakukan sesuatu ...} // setelah} Sinkronisasi juga dapat digunakan untuk fungsi statis:
Metode void statis yang disinkronkan publik () {}
Penting untuk dicatat di tempat ini bahwa kunci yang disinkronkan ditambahkan ke objek kelas saat ini, sehingga semua panggilan ke metode ini harus mendapatkan kunci objek kelas.
Meskipun disinkronkan dapat memastikan keamanan utas objek atau segmen kode, menggunakan sinkronisasi saja masih belum cukup untuk mengontrol interaksi utas dengan logika yang kompleks. Untuk mencapai interaksi antara beberapa utas, metode tunggu () dan beri tahu () dari objek juga diperlukan.
Penggunaan Khas:
disinkronkan (obj) {while (<?>) {obj.wait (); // Lanjutkan untuk mengeksekusi setelah menerima pemberitahuan. }} Sebelum menggunakan metode tunggu (), Anda perlu mendapatkan kunci objek. Ketika metode tunggu () dieksekusi, utas saat ini dapat melepaskan kunci eksklusif OBJ untuk digunakan oleh utas lain.
Saat menunggu utas di OBJ untuk menerima obj.notify (), itu dapat mendapatkan kembali kunci eksklusif OBJ dan terus berjalan. Perhatikan bahwa metode notify () adalah untuk membangkitkan utas secara acak yang menunggu pada objek saat ini.
Berikut adalah implementasi antrian pemblokiran:
Public Class BlockQueue {Private List List = New ArrayList (); objek yang disinkronkan publik pop () melempar interruptedException {while (list.size () == 0) {this.wait (); } if (list.size ()> 0) {return list.remove (0); } else {return null; }} public yang disinkronkan objek put (objek obj) {list.add (obj); this.notify (); }} Sinkronisasi dan tunggu () dan beri tahu () harus menjadi keterampilan dasar yang harus dikuasai pengembang Java.
Reentrantlock Reentrantlock Lock
Reentrantlock disebut reentrantlock. Ini memiliki fitur yang lebih kuat daripada disinkronkan, dapat mengganggu dan waktu. Dalam kasus konkurensi tinggi, ia memiliki keunggulan kinerja yang jelas dibandingkan disinkronkan.
Reentrantlock menyediakan kunci yang adil dan tidak adil. Kunci yang adil adalah yang pertama di pertama dari kunci, dan tidak ada kunci yang adil dapat dipotong sejalan. Tentu saja, dari perspektif kinerja, kinerja kunci yang tidak adil jauh lebih baik. Oleh karena itu, dengan tidak adanya kebutuhan khusus, kunci yang tidak adil harus lebih disukai, tetapi disinkronkan menyediakan industri penguncian tidak sepenuhnya adil. Reentrantlock dapat menentukan apakah kunci itu adil saat membangun.
Saat menggunakan kunci masuk kembali, pastikan untuk melepaskan kunci di akhir program. Secara umum, kode untuk melepaskan kunci harus ditulis akhirnya. Kalau tidak, jika pengecualian program terjadi, Loack tidak akan pernah dirilis. Kunci yang disinkronkan secara otomatis dilepaskan oleh JVM di akhir.
Penggunaan klasik adalah sebagai berikut:
Coba {if (lock.trylock (5, timeunit.seconds)) {// Jika telah dikunci, coba tunggu 5s untuk melihat apakah kunci dapat diperoleh. Jika kunci tidak dapat diperoleh setelah 5s, kembalikan false untuk melanjutkan eksekusi // lock.lockinterricture (); dapat menanggapi acara interupsi coba {// operasi} akhirnya {lock.unlock (); }}} catch (InterruptedException e) {E.PrintStackTrace (); // Saat utas saat ini terputus (interrupt), interruptedException akan dilemparkan}Reentrantlock menyediakan beragam fungsi kontrol kunci, dan secara fleksibel menerapkan metode kontrol ini untuk meningkatkan kinerja aplikasi. Namun, tidak terlalu disarankan untuk menggunakan reentrantlock di sini. Reentry Lock adalah alat pengembangan canggih yang disediakan di JDK.
Readwritelock baca dan tulis kunci
Membaca dan menulis pemisahan adalah ide pemrosesan data yang sangat umum. Ini harus dianggap sebagai teknologi yang diperlukan di SQL. ReadWritelock adalah kunci pemisahan baca-tulis yang disediakan di JDK5. Baca dan tulis kunci pemisahan dapat secara efektif membantu mengurangi kompetisi kunci untuk meningkatkan kinerja sistem. Skenario penggunaan untuk pemisahan baca dan tulis terutama jika dalam sistem, jumlah operasi baca jauh lebih besar daripada operasi penulisan. Cara menggunakannya adalah sebagai berikut:
Private ReentrantReadWritelock ReadWritelock = baru reentrantreadwritelock (); private lock readlock = readwritelock.readlock (); private lock writelock = readwritelock.writelock (); public objek handleread () throws interruptEcception {try {readlock.lock.lock.lock. Thread.sleep (1000); nilai pengembalian; } akhirnya {readlock.unlock (); }} public objek handleread () melempar interruptedException {try {writelock.lock (); Thread.sleep (1000); nilai pengembalian; } akhirnya {writelock.unlock (); }} Objek Kondisi
Objek ConditionD digunakan untuk mengoordinasikan kolaborasi kompleks antara beberapa utas. Terutama terkait dengan kunci. Suatu instance kondisi yang terikat ke kunci dapat dihasilkan melalui metode newCondition () di antarmuka kunci. Hubungan antara objek kondisi dan kunci seperti menggunakan dua fungsi objek.Wait (), objek.notify () dan kata kunci yang disinkronkan.
Di sini Anda dapat mengekstrak kode sumber arrayblockingqueue:
public class ArrayBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable {/** Main lock guarding all access */final ReentrantLock lock;/** Condition for waiting take */private final Condition notEmpty;/** Condition for waiting puts */private final Condition notFull;public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IlegalargumentException (); this.items = objek baru [kapasitas]; lock = baru reentrantlock (adil); notempty = lock.newcondition (); // Hasilkan kondisi tigi = lock.newcondition ();} public void put (e e) melempar interruptedException {checknotnull (e); final reentrantlock lock = this.lock; lock.lockinterricture (); coba {while (count == items.length) notfull.Await (); masukkan (e); } akhirnya {lock.unlock (); }} private void insert (e x) {item [putIndex] = x; putIndex = inc (putIndex); ++ Count; notempty.signal (); // notifikasi} public e Take () melempar interruptedException {final reentrantlock lock = this.lock; lock.lockinterricture (); coba {while (count == 0) // jika antriannya kosong notempty.aWait (); // Kemudian antrian konsumen harus menunggu ekstrak pengembalian sinyal yang tidak kosong (); } akhirnya {lock.unlock (); }} private e extract () {objek akhir [] item = this.items; E x = ini. <e> cast (item [TakeIndex]); Item [TakeIndex] = null; TakeIndex = Inc (TakeIndex); --menghitung; tistrull.signal (); // beri tahu put () bahwa antrian utas memiliki ruang bebas pengembalian x;} // kode lain} Semaphore Semaphore <BR /> Semaphore menyediakan metode kontrol yang lebih kuat untuk kolaborasi multi-thread. Semaphore adalah perpanjangan ke kunci. Apakah itu kunci internal yang disinkronkan atau reentrantlock, satu utas memungkinkan akses ke sumber daya pada satu waktu, sedangkan semaphore dapat menentukan bahwa beberapa utas mengakses sumber daya pada saat yang sama. Dari konstruktor, kita bisa melihat:
semaphore publik (izin int) {}
semaphore publik (izin int, boolean fair) {} // dapat menentukan apakah itu adil
Izin Menentukan Buku Akses untuk Semaphore, yang berarti berapa banyak lisensi yang dapat diterapkan pada saat yang sama. Ketika setiap utas hanya berlaku untuk satu lisensi pada satu waktu, ini setara dengan menentukan berapa banyak utas yang dapat mengakses sumber daya tertentu secara bersamaan. Berikut adalah metode utama untuk digunakan:
public void acquire () melempar InterruptedException {} // Cobalah untuk mendapatkan izin akses. Jika tidak tersedia, utas akan menunggu, mengetahui bahwa utas melepaskan izin atau utas saat ini terganggu.
public void AcquireUninterruptible () {} // Mirip dengan Acquire (), tetapi tidak menanggapi interupsi.
Public boolean tryacquire () {} // Cobalah untuk mendapatkannya, benar jika berhasil, jika tidak salah. Metode ini tidak akan menunggu dan akan segera kembali.
Public Boolean TREACQUIRE (Timeout Long, TimeUnit Unit) melempar InterruptedException {} // Berapa lama waktu yang dibutuhkan untuk menunggu
Rilis public void () // digunakan untuk melepaskan lisensi setelah sumber daya akses di tempat selesai sehingga utas lain yang menunggu izin dapat mengakses sumber daya.
Mari kita lihat contoh menggunakan semaphores yang disediakan dalam dokumen JDK. Contoh ini menjelaskan cara mengontrol akses sumber daya melalui semafor.
Pool kelas publik {private static final int max_available = 100; private final semaphore tersedia = semaphore baru (max_available, true); objek publik getItem () melempar interruptedException {tersedia.acquire (); // Ajukan lisensi // Hanya 100 utas yang dapat masuk untuk mendapatkan item yang tersedia secara bersamaan, // Jika lebih dari 100, Anda perlu menunggu pengembalian getNextAvailableItem ();} public void putitem (objek x) {// Masukkan item yang diberikan kembali ke dalam kumpulan dan tandai sebagai tidak digunakan jika (markasunused (x)) {tersedia. // Menambahkan item yang tersedia, lepaskan lisensi, dan utas yang meminta sumber daya diaktifkan}} // misalnya referensi saja, objek yang dilindungi data non-real item [] = objek baru [max_available]; // Digunakan untuk Object Pool Multiplexing Objects Dilindungi Boolean [] digunakan = boolean baru [max_available]; // Fungsi Markup Objek Sinkronisasi Dilindungi GetNextAvailableItem () {for (int i = 0; i <max_available; ++ i) {if (! Bekas [i]) {bekas [i] = true; mengembalikan item [i]; }} return null;} protected yang disinkronkan boolean markasunused (item objek) {for (int i = 0; i <max_available; ++ i) {if (item == item [i]) {if (digunakan [i]) {bekas [i] = false; Kembali Benar; } else {return false; }}} return false;}} Contoh ini hanya mengimplementasikan kumpulan objek dengan kapasitas maksimum 100. Oleh karena itu, ketika ada 100 permintaan objek pada saat yang sama, kumpulan objek akan memiliki kekurangan sumber daya, dan utas yang gagal mendapatkan sumber daya perlu menunggu. Ketika utas selesai menggunakan objek, ia perlu mengembalikan objek ke kumpulan objek. Pada saat ini, karena sumber daya yang tersedia meningkat, utas yang menunggu sumber daya dapat diaktifkan.
Threadlocal Thread Variabel Lokal <BR /> Setelah baru mulai menghubungi ThreadLocal, sulit bagi saya untuk memahami skenario penggunaan variabel lokal utas ini. Saat melihat ke belakang sekarang, ThreadLocal adalah solusi untuk akses bersamaan ke variabel antara beberapa utas. Tidak seperti metode penguncian yang disinkronkan dan lainnya, Threadlocal tidak memberikan kunci sama sekali, tetapi menggunakan metode pertukaran ruang untuk waktu untuk menyediakan setiap utas dengan salinan variabel independen untuk memastikan keamanan utas. Oleh karena itu, ini bukan solusi untuk berbagi data.
Threadlocal adalah ide yang bagus untuk menyelesaikan masalah keamanan utas. Ada peta di kelas threadlocal yang menyimpan salinan variabel untuk setiap utas. Kunci elemen dalam peta adalah objek utas, dan nilainya sesuai dengan salinan variabel untuk utas. Karena nilai kunci tidak dapat diulang, masing -masing "objek utas" sesuai dengan "salinan variabel" dari utas, dan mencapai keamanan utas.
Ini sangat penting. Dalam hal kinerja, Threadlocal tidak memiliki kinerja absolut. Ketika volume konkurensi tidak terlalu tinggi, kinerja penguncian akan lebih baik. Namun, sebagai serangkaian solusi yang aman-utas yang sama sekali tidak terkait dengan kunci, menggunakan threadlocal dapat mengurangi persaingan kunci sampai batas tertentu dalam persaingan konkurensi tinggi atau persaingan yang sengit.
Berikut adalah penggunaan threadlocal sederhana:
Metode Public Class TestNum {// Overwrite ThreadLocal's InitialValue () melalui kelas dalam anonim, tentukan nilai awal threadlocal seqnum = threadlocal baru () {public integer initialValue () {return 0; }}; // dapatkan nilai urutan berikutnya int int getNextNum () {seqnum.set (seqnum.get () + 1); return seqnum.get ();} public static void main (string [] args) {testnum sn = new testnum (); // 3 utas berbagi sn, masing -masing menghasilkan urutan urut testclient t1 = testclient baru (sn); TestClient T2 = TestClient baru (SN); TestClient T3 = TestClient baru (SN); t1.start (); t2.start (); t3.start (); } private static class testclient memperluas thread {private testnum sn; TestClient publik (testnum sn) {this.sn = sn; } public void run () {for (int i = 0; i <3; i ++) {// Setiap utas menghasilkan 3 nilai urutan System.out.println ("Thread [" + thread.currentThread (). getName () + "] -> sn [" + sn.getNextNum () + "]"); }}}} Hasil output:
Thread [thread-0]> sn [1]
Thread [thread-1]> sn [1]
Thread [Thread-2]> Sn [1]
Thread [thread-1]> sn [2]
Thread [thread-0]> sn [2]
Thread [thread-1]> sn [3]
Thread [thread-2]> sn [2]
Thread [thread-0]> sn [3]
Thread [Thread-2]> Sn [3]
Informasi hasil output dapat ditemukan bahwa meskipun angka urutan yang dihasilkan oleh masing -masing utas berbagi instance testNum yang sama, mereka tidak saling mengganggu, tetapi masing -masing menghasilkan angka urutan independen. Ini karena ThreadLocal memberikan salinan terpisah untuk setiap utas.
Kinerja kunci dan optimalisasi "kunci" adalah salah satu metode sinkronisasi yang paling umum digunakan. Dalam pengembangan normal, Anda sering dapat melihat banyak siswa secara langsung menambahkan sepotong kode besar ke kunci. Beberapa siswa hanya dapat menggunakan satu metode kunci untuk menyelesaikan semua masalah berbagi. Jelas, pengkodean seperti itu tidak dapat diterima. Terutama di lingkungan konkurensi yang tinggi, kompetisi kunci yang sengit akan menyebabkan degradasi kinerja program yang lebih jelas. Oleh karena itu, penggunaan kunci rasional terkait langsung dengan kinerja program.
1. Overhead Thread <BR /> Dalam kasus multi-core, menggunakan multi-threading dapat secara signifikan meningkatkan kinerja sistem. Namun, dalam situasi aktual, menggunakan multi-threading akan menambah overhead sistem tambahan. Selain konsumsi sumber daya dari tugas-tugas sistem inti tunggal itu sendiri, aplikasi multi-threaded juga perlu mempertahankan informasi unik multi-threaded tambahan. Misalnya, metadata utas itu sendiri, penjadwalan utas, switching konteks utas, dll.
2. Kurangi waktu penahanan kunci
Dalam program yang menggunakan kunci untuk kontrol bersamaan, ketika kunci bersaing, waktu holding kunci dari satu utas memiliki hubungan langsung dengan kinerja sistem. Jika utas menahan kunci untuk waktu yang lama, kompetisi untuk kunci akan lebih intens. Oleh karena itu, selama proses pengembangan program, waktu untuk menempati kunci tertentu harus diminimalkan untuk mengurangi kemungkinan pengecualian timbal balik antara utas. Misalnya, kode berikut:
SyncMEHOD void yang disinkronkan publik () {beforemethod (); mutexMethod (); aftermethod ();} Jika saja metode mutexmethod () dalam contoh ini sinkron, tetapi pada beforemethod () dan aftermethod () tidak memerlukan kontrol sinkronisasi. Jika Beforemethod () dan AfterMethod () adalah metode kelas berat, akan memakan waktu lama untuk CPU. Pada saat ini, jika konkurensi besar, menggunakan skema sinkronisasi ini akan menyebabkan peningkatan besar dalam utas menunggu. Karena utas yang saat ini dieksekusi akan melepaskan kunci hanya setelah semua tugas telah dieksekusi.
Berikut ini adalah solusi yang dioptimalkan, yang hanya disinkronkan bila perlu, sehingga waktu untuk utas untuk menahan kunci dapat dikurangi secara signifikan dan throughput sistem dapat ditingkatkan. Kodenya adalah sebagai berikut:
public void syncmeHod () {beforemethod (); disinkronkan (ini) {mutexmethod ();} aftermethod ();} 3. Kurangi ukuran partikel pengunci
Mengurangi granularitas kunci juga merupakan sarana yang efektif untuk melemahkan kompetisi untuk kunci multi-threaded. Skenario penggunaan khas dari teknologi ini adalah kelas ConcurrenthashMap. Dalam hashmap biasa, setiap kali operasi ADD () GET () dilakukan pada koleksi, kunci objek pengumpulan selalu diperoleh. Operasi ini sepenuhnya merupakan perilaku sinkron karena kunci ada di seluruh objek pengumpulan. Oleh karena itu, dalam konkurensi tinggi, persaingan kunci yang sengit akan mempengaruhi throughput sistem.
Jika Anda telah membaca kode sumber, Anda harus tahu bahwa HashMap diimplementasikan dalam daftar array + tertaut. ConcurrenthashMap membagi seluruh hashmap menjadi beberapa segmen (segmen), dan setiap segmen adalah sub-hashmap. Jika Anda perlu menambahkan entri tabel baru, Anda tidak mengunci hashmap. Garis dua puluh pencarian akan mendapatkan bagian di mana entri tabel harus disimpan sesuai dengan kode hash, dan kemudian mengunci bagian dan menyelesaikan operasi put (). Dengan cara ini, dalam lingkungan multi-threaded, jika banyak utas melakukan operasi penulisan pada saat yang sama, selama item yang ditulis tidak ada di segmen yang sama, paralelisme sejati dapat dicapai di antara utas. Untuk implementasi spesifik, saya berharap pembaca akan meluangkan waktu untuk membaca kode sumber kelas ConcurrenthashMap, jadi saya tidak akan terlalu menggambarkannya di sini.
4. Pemisahan Kunci <BR /> ReadWritelock membaca dan menulis kunci yang disebutkan sebelumnya, lalu perpanjangan pemisahan baca dan tulis adalah pemisahan kunci. Kode sumber pemisahan kunci juga dapat ditemukan di JDK.
Kelas publik LinkedBlockingQueue memperluas AbstractQueue mengimplementasikan blockingqueue, java.io.serializable {/*lock hold dengan take, jajak pendapat, dll/final swasta reentrantlock takelock = baru reentrantlock ();/** tunggu antrian untuk menunggu*/kondisi akhir nottempty = takelock.newcondition; Reentrantlock putlock = baru reentrantlock ();/** tunggu antrian untuk menunggu*/kondisi akhir pribadi itfull = putlock.newcondition (); public e take () melempar interrupted exception {ex; int c = -1; final atomicinteger count = this.count; final reentrantlock takelock = this.takelock; takelock.lockinterricture (); // Tidak mungkin ada dua utas untuk membaca data pada saat yang sama coba {while (count.get () == 0) {// Jika tidak ada data yang tersedia, tunggu pemberitahuan put () notempty.aWait (); } x = dequeue (); // hapus item c = count.getAndDecrement (); // ukuran minus 1 if (c> 1) notempty.signal (); // beri tahu operasi ambil ()} akhirnya {takelock.unlock (); // lepaskan kunci} if (c == kapasitas) sinyal tidak full (); // notify put () operasi, sudah ada return ruang bebas x;} public void put (e e) melempar interruptedException {if (e == null) melempar nullpointerException baru (); // Catatan: Konvensi di semua put/Take/etc adalah untuk preset var lokal // memegang jumlah negatif untuk menunjukkan kegagalan kecuali diatur. int c = -1; Node <E> node = node baru (e); final reentrantlock putlock = this.putlock; final atomicinteger count = this.count; putlock.lockinterricture (); // Tidak mungkin ada dua utas menempatkan data pada saat yang sama coba { / * * Perhatikan bahwa hitungan digunakan di Wait Guard meskipun * tidak dilindungi oleh kunci. Ini berfungsi karena Count dapat * hanya berkurang pada titik ini (semua put lainnya ditutup * dengan kunci), dan kami (atau beberapa put lainnya) * ditandatangani jika pernah berubah dari kapasitas. Demikian pula * untuk semua penggunaan penghitungan lainnya pada penjaga menunggu lainnya. */ while (count.get () == kapasitas) {// Jika antriannya penuh, tunggu itfull.Await (); } enqueue (node); // Bergabunglah dengan antrian c = count.getAndIncrement (); // size plus 1 if (c + 1 <kapasitas) tiSfull.signal (); // Beri tahu utas lain jika ada cukup ruang} akhirnya {putlock.unlock (); // Lepaskan kunci} if (c == 0) SignalNotEmpty (); // Setelah penyisipan berhasil, beri tahu operasi () untuk membaca data} // kode lain}Apa yang perlu dijelaskan di sini adalah bahwa fungsi Take () dan Put () tidak tergantung satu sama lain, dan tidak ada hubungan kompetisi kunci di antara mereka. Anda hanya perlu bersaing untuk Takelock dan Putlock dalam metode masing -masing Take () dan put (). Dengan demikian, kemungkinan persaingan kunci melemah.
5. Kunci Kasar <BR /> Pengurangan waktu kunci yang disebutkan di atas dan granularitas dilakukan untuk memenuhi waktu terpendek bagi setiap utas untuk menahan kunci. Namun, gelar harus dipahami dalam granularitas. Jika kunci terus diminta, disinkronkan dan dilepaskan, itu akan mengkonsumsi sumber daya yang berharga dari sistem dan meningkatkan overhead sistem.
Yang perlu kita ketahui adalah bahwa ketika mesin virtual bertemu serangkaian permintaan dan pelepasan kontinu dari kunci yang sama, itu akan mengintegrasikan semua operasi kunci ke dalam satu permintaan ke kunci, sehingga mengurangi jumlah permintaan kunci. Operasi ini disebut Lock Rreed. Berikut adalah demonstrasi contoh integrasi:
public void syncmehoD () {disinkronkan (lock) {method1 ();} disinkronkan (lock) {method2 ();}} bentuk setelah integrasi jvm: public void syncmehod () {disinkronkan (lock) {method1 (); Method2 ();}}Oleh karena itu, integrasi semacam itu memberi pengembang kami efek demonstrasi yang baik pada pemahaman granularitas kunci.
Komputasi paralel tanpa kunci <BR /> di atas telah menghabiskan banyak waktu berbicara tentang penguncian, dan juga disebutkan bahwa penguncian akan membawa overhead sumber daya tambahan untuk switching konteks tertentu. Dalam konkurensi tinggi, persaingan ketat untuk "penguncian" dapat menjadi hambatan sistem. Oleh karena itu, metode sinkronisasi non-blocking dapat digunakan di sini. Metode bebas kunci ini masih dapat memastikan bahwa data dan program mempertahankan konsistensi antara beberapa utas di lingkungan konkurensi yang tinggi.
1. Sinkronisasi/tanpa kunci non-blocking
Metode sinkronisasi non-blocking sebenarnya tercermin dalam threadlocal sebelumnya. Setiap utas memiliki salinan variabel independennya sendiri, jadi tidak perlu menunggu satu sama lain saat menghitung secara paralel. Di sini, penulis terutama merekomendasikan metode kontrol konkurensi bebas-kunci yang lebih penting berdasarkan algoritma CAS Bandingkan dan bertukar.
Proses algoritma CAS: Ini berisi 3 parameter CAS (V, E, N). V mewakili variabel yang akan diperbarui, E mewakili nilai yang diharapkan, dan N mewakili nilai baru. Nilai V akan diatur ke N hanya ketika nilai V sama dengan nilai E. Jika nilai V berbeda dari nilai E, itu berarti bahwa utas lain telah melakukan pembaruan, dan utas saat ini tidak melakukan apa -apa. Akhirnya, CAS mengembalikan nilai sebenarnya dari V. saat ini ketika mengoperasikan CAS, ia dilakukan dengan sikap yang optimis, dan selalu percaya bahwa itu dapat berhasil menyelesaikan operasi. Ketika beberapa utas menggunakan CAS untuk mengoperasikan variabel pada saat yang sama, hanya satu yang akan menang dan diperbarui dengan sukses, sementara sisa Junhui gagal. Utas yang gagal tidak akan ditangguhkan, hanya diberitahu bahwa kegagalan diizinkan, dan diizinkan untuk mencoba lagi, dan tentu saja utas yang gagal juga akan memungkinkan operasi untuk ditinggalkan. Berdasarkan prinsip ini, operasi CAS tepat waktu tanpa kunci, dan utas lainnya juga dapat mendeteksi gangguan pada utas saat ini dan menanganinya dengan tepat.
2. Operasi Berat Atom
Java.util.concurrent. Siswa yang tertarik dapat terus melacak kode tingkat asli. Saya tidak akan memposting implementasi kode permukaan di sini.
Berikut ini terutama menggunakan contoh untuk menunjukkan kesenjangan kinerja antara metode sinkronisasi biasa dan sinkronisasi bebas kunci:
testatomik kelas publik {private static final int max_threads = 3; private static final int tugas_count = 3; private static final int target_count = 100 * 10000; akun atomicinteger pribadi = atomicinteger new (0); classInzed int syncronisasi (gets {return return; {return;} synchroned {return;} synchronisasi () Sinchronic () {return ++ count;} Synchronical Int count; Runnable {string name; waktu mulai yang lama; Testatomic out; Syncthread publik (testatomic o, starttime panjang) {this.out = o; this.starttime = startTime; } @Override public void run () {int v = out.inc (); while (v <target_count) {v = out.inc (); } long endtime = system.currentTimeMillis (); System.out.println ("SyncThread pengeluaran:" + (endtime - startTime) + "ms" + ", v =" + v); }} kelas publik Atomicthread mengimplementasikan runnable {string name; waktu mulai yang lama; atomicthread publik (startTime panjang) {this.starttime = startTime; } @Override public void run () {int v = Account.incrementandget (); while (v <target_count) {v = account.incrementandget (); } long endtime = system.currentTimeMillis (); System.out.println ("Atomicthread pengeluaran:" + (endtime - startTime) + "ms" + ", v =" + v); }}@Testpublic void testsync () melempar InterruptedException {ExecutorService exe = executors.newfixedThreadPool (max_threads); Long StartTime = System.CurrentTimeMillis (); Syncthread sync = Syncthread baru (ini, startTime); untuk (int i = 0; i <task_count; i ++) {exe.submit (sync); } Thread.sleep (10000);}@testpublic void testatomic () melempar InterruptedException {ExecutorService exe = executors.newfixedThreadPool (max_threads); Long StartTime = System.CurrentTimeMillis (); Atomicthread atomic = atomicthread baru (startTime); untuk (int i = 0; i <task_count; i ++) {exe.submit (atomic); } Thread.sleep (10000);}} Hasil tes adalah sebagai berikut:
testsync ():
SYNCTHREAD BELAJAR: 201MS, V = 1000002
SYNCTHREAD BELAJAR: 201MS, V = 1000000
SYNCTHREAD BELAJAR: 201MS, V = 1000001
testatomic ():
Pengeluaran Atomicthread: 43ms, V = 1000000
Pengeluaran Atomicthread: 44ms, V = 1000001
Pengeluaran Atomicthread: 46ms, V = 1000002
Saya percaya bahwa hasil tes semacam itu dengan jelas akan mencerminkan perbedaan kinerja antara penguncian internal dan algoritma sinkronisasi non-blocking. Oleh karena itu, penulis merekomendasikan secara langsung menganggap kelas atom ini di bawah atom.
Kesimpulan
Akhirnya, saya telah menyelesaikan hal -hal yang ingin saya ungkapkan. Bahkan, masih ada beberapa kelas seperti Countdownlatch yang belum disebutkan. Namun, apa yang disebutkan di atas jelas merupakan inti dari pemrograman bersamaan. Mungkin beberapa pembaca dapat melihat banyak poin pengetahuan seperti itu di internet, tetapi saya masih berpikir bahwa hanya dengan perbandingan pengetahuan dapat ditemukan dalam skenario penggunaan yang sesuai. Oleh karena itu, ini juga alasan mengapa editor telah menyusun artikel ini, dan saya harap artikel ini dapat membantu lebih banyak siswa.
Di atas adalah semua konten artikel ini. Saya berharap ini akan membantu untuk pembelajaran semua orang dan saya harap semua orang akan lebih mendukung wulin.com.