JavaRush /Blog Jawa /Random-JV /Fundamentals of Concurrency: Deadlocks and Object Monitor...
Snusmum
tingkat
Хабаровск

Fundamentals of Concurrency: Deadlocks and Object Monitors (bagean 1, 2) (terjemahan artikel)

Diterbitake ing grup
Sumber artikel: http://www.javacodegeeks.com/2015/09/concurrency-fundamentals-deadlocks-and-object-monitors.html Dikirim dening Martin Mois Artikel iki minangka bagéan saka kursus Java Concurrency Fundamentals . Ing kursus iki, sampeyan bakal sinau babagan sihir paralelisme. Sampeyan bakal sinau dhasar paralelisme lan kode paralel, lan dadi akrab karo konsep kayata atomicity, sinkronisasi, lan safety thread. Delengen kene !

Isi

1. Liveness  1.1 Deadlock  1.2 Keluwen 2. Obyek ngawasi karo Enteni () lan kabar ()  2.1 Nested pamblokiran diselarasake karo Enteni () lan notifikasi ()  2.2 Kahanan ing pamblokiran diselarasake 3. Desain kanggo multi-threading  3.1 obyek Immutable  3.2 Desain API  3.3 Panyimpenan benang lokal
1. Vitalitas
Nalika ngembangake aplikasi sing nggunakake paralelisme kanggo nggayuh tujuane, sampeyan bisa uga nemoni kahanan sing beda-beda benang bisa mblokir siji liyane. Yen aplikasi mlaku luwih alon tinimbang samesthine ing kahanan iki, kita bakal ujar manawa aplikasi kasebut ora mlaku kaya sing dikarepake. Ing bagean iki, kita bakal nliti masalah sing bisa ngancam kelangsungan urip aplikasi multi-thread.
1.1 Pamblokiran bebarengan
Istilah deadlock kondhang ing antarane pangembang piranti lunak lan malah umume pangguna biasa nggunakake saka wektu kanggo wektu, sanajan ora mesthi bener. Tegese, istilah iki tegese saben rong (utawa luwih) utas nunggu thread liyane ngeculake sumber sing dikunci, dene utas pisanan dhewe wis ngunci sumber daya sing dienteni dening sing kapindho: Supaya luwih ngerti masalah, deleng Thread 1: locks resource A, waits for resource B Thread 2: locks resource B, waits for resource A kode ing ngisor iki: public class Deadlock implements Runnable { private static final Object resource1 = new Object(); private static final Object resource2 = new Object(); private final Random random = new Random(System.currentTimeMillis()); public static void main(String[] args) { Thread myThread1 = new Thread(new Deadlock(), "thread-1"); Thread myThread2 = new Thread(new Deadlock(), "thread-2"); myThread1.start(); myThread2.start(); } public void run() { for (int i = 0; i < 10000; i++) { boolean b = random.nextBoolean(); if (b) { System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 1."); synchronized (resource1) { System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 1."); System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 2."); synchronized (resource2) { System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 2."); } } } else { System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 2."); synchronized (resource2) { System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 2."); System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 1."); synchronized (resource1) { System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 1."); } } } } } } Nalika sampeyan bisa ndeleng saka kode ing ndhuwur, loro Utas miwiti lan nyoba kanggo ngunci loro sumber daya statis. Nanging kanggo deadlocking, kita kudu urutan beda kanggo loro Utas, supaya kita nggunakake Kayata saka obyek Random kanggo milih sumber daya sing arep dikunci dhisik. Yen variabel boolean b bener, banjur resource1 dikunci dhisik, banjur thread nyoba ndarbeni kunci kanggo resource2. Yen b iku palsu, thread ngunci resource2 banjur nyoba kanggo ndarbeni resource1. Program iki ora perlu dawa kanggo entuk deadlock pisanan, i.e. Program bakal macet ing salawas-lawase yen kita ora ngganggu: [thread-1] Trying to lock resource 1. [thread-1] Locked resource 1. [thread-1] Trying to lock resource 2. [thread-1] Locked resource 2. [thread-2] Trying to lock resource 1. [thread-2] Locked resource 1. [thread-1] Trying to lock resource 2. [thread-1] Locked resource 2. [thread-2] Trying to lock resource 2. [thread-1] Trying to lock resource 1. Ing larikan iki, tread-1 wis entuk kunci resource2 lan ngenteni kunci resource1, dene tread-2 duwe kunci resource1 lan ngenteni resource2. Yen kita nyetel nilai variabel boolean b ing kode ing ndhuwur dadi bener, kita ora bakal bisa mirsani deadlock amarga urutan kunci panjalukan thread-1 lan thread-2 mesthi padha. Ing kahanan iki, salah siji saka rong utas bakal njupuk kunci pisanan lan banjur njaluk kaloro, kang isih kasedhiya amarga thread liyane nunggu kunci pisanan. Umumé, kita bisa mbedakake kahanan sing perlu kanggo deadlock: - Eksekusi bareng: Ana sumber daya sing bisa diakses mung siji thread ing sembarang wektu. - Resource Hold: Nalika entuk siji sumber, thread nyoba kanggo ndarbeni kunci liyane ing sawetara sumber unik. - Ora ana preemption: Ora ana mekanisme kanggo ngeculake sumber daya yen siji thread nyekel kunci kanggo wektu tartamtu. - Enteni Circular: Sajrone eksekusi, ana koleksi utas ing ngendi loro (utawa luwih) utas ngenteni saben liyane kanggo ngeculake sumber daya sing wis dikunci. Senajan dhaftar kahanan katon dawa, iku ora aneh kanggo aplikasi multi-Utas uga mbukak masalah deadlock. Nanging sampeyan bisa nyegah yen sampeyan bisa mbusak salah siji saka kondisi ing ndhuwur: - Eksekusi bareng: kondisi iki asring ora bisa dibusak nalika sumber daya kudu digunakake mung siji wong. Nanging iki ora kudu dadi alesan. Nalika nggunakake sistem DBMS, solusi sing bisa ditindakake, tinimbang nggunakake kunci pesimis ing sawetara baris meja sing kudu dianyari, yaiku nggunakake teknik sing diarani Optimistic Locking . - Cara supaya ora nyekel sumber daya nalika ngenteni sumber daya eksklusif liyane yaiku ngunci kabeh sumber daya sing dibutuhake ing wiwitan algoritma lan ngeculake kabeh yen ora bisa ngunci kabeh bebarengan. Mesthi wae, iki ora bisa ditindakake, mbok menawa sumber daya sing mbutuhake ngunci ora dingerteni sadurunge, utawa pendekatan iki mung bakal mbuwang sumber daya. - Yen kunci ora bisa dipikolehi langsung, cara kanggo ngliwati deadlock sing bisa ditindakake yaiku ngenalake wektu entek. Contone, kelas ReentrantLocksaka SDK menehi kemampuan kanggo nyetel tanggal kadaluwarsa kanggo kunci. - Kaya sing kita deleng saka conto ing ndhuwur, deadlock ora kedadeyan yen urutan panjalukan ora beda-beda ing antarane benang sing beda. Iki gampang dikontrol yen sampeyan bisa nglebokake kabeh kode pamblokiran dadi siji cara sing kudu ditindakake kabeh benang. Ing aplikasi sing luwih maju, sampeyan bisa uga nimbang ngetrapake sistem deteksi deadlock. Kene sampeyan kudu ngleksanakake sawetara semblance saka ngawasi thread, kang saben thread laporan sing wis kasil angsal kunci lan nyoba kanggo ndarbeni kunci. Yen benang lan kunci dimodelake minangka grafik sing diarahake, sampeyan bisa ndeteksi nalika rong utas beda nyekel sumber daya nalika nyoba ngakses sumber daya liyane sing dikunci bebarengan. Yen sampeyan bisa meksa utas pamblokiran kanggo ngeculake sumber daya sing dibutuhake, sampeyan bisa ngrampungake kahanan buntu kanthi otomatis.
1.2 Pasa
Penjadwal mutusake benang ing negara RUNNABLE sing kudu ditindakake sabanjure. Kaputusan adhedhasar prioritas utas; mulane, Utas karo prioritas ngisor nampa wektu CPU kurang dibandhingake karo prioritas luwih. Apa sing katon kaya solusi sing cukup uga bisa nyebabake masalah yen disalahake. Yen utas prioritas dhuwur biasane dieksekusi, mula benang prioritas kurang katon keluwen amarga ora entuk cukup wektu kanggo nindakake pakaryan kanthi bener. Mulane, dianjurake kanggo nyetel prioritas utas mung yen ana alesan sing kuat kanggo nglakoni. Conto sing ora jelas saka keluwen benang diwenehi, contone, kanthi metode finalize (). Iki menehi cara kanggo basa Jawa nglakokake kode sadurunge obyek dikumpulake. Nanging yen sampeyan ndeleng prioritas thread finalizing, sampeyan bakal sok dong mirsani sing ora mbukak karo prioritas paling dhuwur. Akibate, keluwen thread occurs nalika obyek finalize () cara nglampahi kakehan wektu relatif kanggo liyane saka kode. Masalah liyane karo wektu eksekusi muncul saka kasunyatan sing ora ditetepake ing urutan apa benang ngliwati blok sing disinkronake. Nalika akeh utas paralel ngliwati sawetara kode sing dibingkai ing blok sing disinkronake, bisa uga sawetara benang kudu ngenteni luwih suwe tinimbang liyane sadurunge mlebu blok kasebut. Ing teori, dheweke bisa uga ora bakal teka. Solusi kanggo masalah iki yaiku sing diarani pamblokiran "adil". Kunci sing adil njupuk wektu tunggu benang nalika nemtokake sapa sing bakal lulus sabanjure. Conto implementasi ngunci adil kasedhiya ing Java SDK: java.util.concurrent.locks.ReentrantLock. Yen konstruktor digunakake kanthi gendéra boolean disetel dadi bener, banjur ReentrantLock menehi akses menyang thread sing wis nunggu paling dawa. Iki njamin ora ana keluwen nanging, ing wektu sing padha, nyebabake masalah ora nggatekake prioritas. Amarga iki, proses prioritas sing luwih murah sing asring ngenteni ing alangan iki bisa mlaku luwih kerep. Paling ora, nanging paling ora, kelas ReentrantLock mung bisa nimbang Utas sing nunggu kunci, i.e. Utas sing cukup kerep diluncurake lan tekan alangi. Yen prioritas utas kurang banget, mula iki ora bakal kelakon asring, lan mulane utas prioritas dhuwur isih bakal ngliwati kunci luwih asring.
2. Monitor obyek bebarengan karo ngenteni () lan ngabari ()
Ing komputasi multi-threaded, kahanan sing umum yaiku sawetara benang pekerja nunggu produsere nggawe sawetara karya kanggo dheweke. Nanging, nalika kita sinau, kanthi aktif nunggu ing daur ulang nalika mriksa nilai tartamtu ora dadi pilihan sing apik babagan wektu CPU. Nggunakake Thread.sleep () cara ing kahanan iki uga ora utamané cocok yen kita arep kanggo miwiti karya kita sanalika sawise rawuh. Kanggo maksud iki, basa pemrograman Jawa nduweni struktur liyane sing bisa digunakake ing skema iki: ngenteni () lan notifikasi (). Enteni () cara, dipun warisaken dening kabeh obyek saka kelas java.lang.Object, bisa digunakake kanggo nundha thread saiki lan ngenteni nganti thread liyane wakes kita nggunakake notify () cara. Supaya bisa digunakake kanthi bener, thread sing nelpon metode Enteni () kudu nyekel kunci sing sadurunge dipikolehi nggunakake tembung kunci sing disinkronake. Nalika Enteni () disebut, kunci dirilis lan Utas ngenteni nganti Utas liyane sing saiki ngemu kunci telpon notify () ing Kayata obyek padha. Ing aplikasi multi-threaded, bisa uga ana luwih saka siji utas sing nunggu kabar ing sawetara obyek. Mulane, ana rong cara kanggo tangi thread: notify () lan notifyAll (). Nalika cara pisanan wakes munggah siji saka Utas nunggu, notifyAll () cara tangi kabeh mau. Nanging elinga yen, kaya tembung kunci sing disinkronake, ora ana aturan sing nemtokake benang sing bakal tangi nalika notifikasi () diarani. Ing conto sing prasaja karo produser lan konsumen, iki ora dadi masalah, amarga kita ora peduli karo benang sing wis tangi. Kode ing ngisor iki nuduhake carane ngenteni () lan notifikasi () bisa digunakake kanggo nimbulaké thread konsumen ngenteni karya anyar kanggo antrian thread produser: package a2; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; public class ConsumerProducer { private static final Queue queue = new ConcurrentLinkedQueue(); private static final long startMillis = System.currentTimeMillis(); public static class Consumer implements Runnable { public void run() { while (System.currentTimeMillis() < (startMillis + 10000)) { synchronized (queue) { try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if (!queue.isEmpty()) { Integer integer = queue.poll(); System.out.println("[" + Thread.currentThread().getName() + "]: " + integer); } } } } public static class Producer implements Runnable { public void run() { int i = 0; while (System.currentTimeMillis() < (startMillis + 10000)) { queue.add(i++); synchronized (queue) { queue.notify(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (queue) { queue.notifyAll(); } } } public static void main(String[] args) throws InterruptedException { Thread[] consumerThreads = new Thread[5]; for (int i = 0; i < consumerThreads.length; i++) { consumerThreads[i] = new Thread(new Consumer(), "consumer-" + i); consumerThreads[i].start(); } Thread producerThread = new Thread(new Producer(), "producer"); producerThread.start(); for (int i = 0; i < consumerThreads.length; i++) { consumerThreads[i].join(); } producerThread.join(); } } Cara utama () miwiti limang benang konsumen lan siji benang produser lan banjur ngenteni nganti rampung. Utas produser banjur nambahake nilai anyar menyang antrian lan ngabari kabeh utas nunggu yen ana kedadeyan. Konsumen entuk kunci antrian (yaiku, siji konsumen acak) banjur turu, bakal diunggahake mengko yen antrian wis kebak maneh. Nalika produser rampung pakaryane, bakal menehi kabar marang kabeh konsumen supaya tangi. Yen kita ora nindakake langkah pungkasan, benang konsumen bakal ngenteni kabar sabanjure amarga kita ora nyetel wektu entek kanggo ngenteni. Nanging, kita bisa nggunakake cara ngenteni (wektu entek dawa) kanggo tangi paling ora sawise sawetara wektu wis liwati.
2.1 Blok sing disinkronake karo ngenteni () lan ngabari ()
Kaya sing kasebut ing bagean sadurunge, nelpon ngenteni () ing monitor obyek mung ngeculake kunci ing monitor kasebut. Kunci liyane sing dicekel dening benang sing padha ora dirilis. Kaya sing gampang dimangerteni, ing saben dina bisa kedadeyan yen thread nelpon ngenteni () terus kunci. Yen benang liyane uga nunggu kunci kasebut, kahanan buntu bisa kedadeyan. Ayo goleki kunci ing conto ing ngisor iki: public class SynchronizedAndWait { private static final Queue queue = new ConcurrentLinkedQueue(); public synchronized Integer getNextInt() { Integer retVal = null; while (retVal == null) { synchronized (queue) { try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } retVal = queue.poll(); } } return retVal; } public synchronized void putInt(Integer value) { synchronized (queue) { queue.add(value); queue.notify(); } } public static void main(String[] args) throws InterruptedException { final SynchronizedAndWait queue = new SynchronizedAndWait(); Thread thread1 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 10; i++) { queue.putInt(i); } } }); Thread thread2 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 10; i++) { Integer nextInt = queue.getNextInt(); System.out.println("Next int: " + nextInt); } } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } } Kaya sing kita sinau sadurunge , nambah sing disinkronake menyang teken metode padha karo nggawe blok sing disinkronake(iki). Ing conto ing ndhuwur, kita ora sengaja nambahake tembung kunci sing disinkronake menyang metode kasebut, banjur nyelarasake antrian karo monitor obyek antrian kanggo ngirim thread iki turu nalika ngenteni nilai sabanjure saka antrian. Banjur, benang saiki ngeculake kunci ing antrian, nanging dudu kunci kasebut. Cara putInt () ngabari thread turu yen nilai anyar wis ditambahake. Nanging kanthi kasempatan kita nambahake tembung kunci sing disinkronake kanggo metode iki uga. Saiki benang kapindho wis turu, isih nyekel kunci. Mulane, thread pisanan ora bisa ngetik cara putInt () nalika kunci dianakaké dening thread kapindho. Akibaté, kita duwe kahanan buntu lan program beku. Yen sampeyan mbukak kode ing ndhuwur, iku bakal kelakon sanalika sawise program wiwit mlaku. Ing urip saben dina, kahanan iki bisa uga ora katon. Kunci sing dianakake dening benang bisa uga gumantung ing paramèter lan kahanan sing ditemoni nalika runtime, lan blok sing diselarasake nyebabake masalah kasebut bisa uga ora cedhak karo kode ing ngendi kita ngenteni () telpon. Iki ndadekake angel nemokake masalah kasebut, utamane amarga bisa kedadeyan sajrone wektu utawa ing beban dhuwur.
2.2 Kahanan ing blok sing disinkronake
Asring sampeyan kudu mriksa manawa sawetara kondisi wis ditemtokake sadurunge nindakake tumindak ing obyek sing disinkronake. Yen sampeyan duwe antrian, contone, sampeyan pengin ngenteni nganti diisi. Mulane, sampeyan bisa nulis cara sing mriksa yen antrian wis kebak. Yen isih kosong, sampeyan ngirim thread saiki kanggo turu nganti tangi: public Integer getNextInt() { Integer retVal = null; synchronized (queue) { try { while (queue.isEmpty()) { queue.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (queue) { retVal = queue.poll(); if (retVal == null) { System.err.println("retVal is null"); throw new IllegalStateException(); } } return retVal; } Kode ing ndhuwur nyinkronake karo antrian sadurunge nelpon ngenteni () banjur ngenteni sawetara wektu nganti paling sethithik siji unsur katon ing antrian. Blok sinkronisasi kapindho maneh nggunakake antrian minangka monitor obyek. Iki nelpon cara jajak pendapat () antrian kanggo entuk nilai. Kanggo tujuan demonstrasi, IllegalStateException dibuwang nalika polling bali null. Iki kedadeyan nalika antrian ora ana unsur sing kudu dijupuk. Nalika sampeyan mbukak conto iki, sampeyan bakal weruh sing IllegalStateException dibuwang asring banget. Sanajan kita nyelarasake kanthi bener nggunakake monitor antrian, pangecualian dibuwang. Alesane yaiku kita duwe rong blok sing disinkronake. Mbayangno kita duwe rong utas sing wis teka ing blok sing diselarasake pisanan. Utas pisanan mlebu blok lan turu amarga antrian kosong. Padha bener kanggo thread kapindho. Saiki loro Utas wis siyaga (matur nuwun kanggo notifyAll () telpon disebut dening Utas liyane kanggo monitor), padha ndeleng Nilai (item) ing antrian ditambahake dening produser. Banjur wong loro teka ing alangi kapindho. Ing kene thread pisanan mlebu lan njupuk nilai saka antrian. Nalika thread kapindho mlebu, antrian wis kosong. Mulane, iku ditampa null minangka Nilai bali saka antrian lan mbalang pangecualian. Kanggo nyegah kahanan kasebut, sampeyan kudu nindakake kabeh operasi sing gumantung saka kahanan monitor ing blok sing padha: public Integer getNextInt() { Integer retVal = null; synchronized (queue) { try { while (queue.isEmpty()) { queue.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } retVal = queue.poll(); } return retVal; } Kene kita nglakokaké polling () cara ing pamblokiran diselarasake padha isEmpty () cara. Thanks kanggo blok sing disinkronake, kita yakin manawa mung siji utas sing nglakokake metode kanggo monitor iki ing wektu tartamtu. Mulane, ora Utas liyane bisa mbusak unsur saka antrian antarane telpon kanggo isEmpty () lan jajak pendapat (). Terusake terjemahan ing kene .
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION