Pambuka
Dadi, kita ngerti manawa ana utas ing Jawa, sing bisa diwaca ing review " Sampeyan Ora Bisa Ngrusak Jawa nganggo Utas: Bagian I - Utas ". Utas dibutuhake kanggo nindakake karya bebarengan. Mulane, kemungkinan banget yen benang bakal sesambungan karo siji liyane. Ayo ngerti kepiye kedadeyan kasebut lan kontrol dhasar apa sing kita duwe.ngasilaken
Metode Thread.yield () misterius lan arang digunakake. Ana akeh variasi saka deskripsi ing Internet. Kanggo sawetara sing nulis babagan sawetara jinis antrian benang, ing ngendi benang bakal mudhun kanthi njupuk prioritas. Ana sing nulis yen thread bakal ngganti status saka mlaku dadi runnable (sanajan ora ana divisi menyang status kasebut, lan Jawa ora mbedakake antarane). Nanging ing kasunyatan, kabeh iku luwih ora dingerteni lan, ing pangertèn, prasaja. Ing topik dokumentasi metode,yield
ana bug " JDK-6416721: (spec thread) Ndandani Thread.yield() javadoc ". Yen sampeyan maca, jelas yen metode kasebut yield
mung menehi rekomendasi menyang panjadwal thread Jawa yen thread iki bisa diwenehi wektu eksekusi sing kurang. Nanging apa sing bakal kelakon, manawa panjadwal bakal ngrungokake rekomendasi lan apa sing bakal ditindakake ing umum gumantung saka implementasine JVM lan sistem operasi. Utawa mungkin saka sawetara faktor liyane. Kabeh kebingungan kasebut kemungkinan amarga mikir maneh babagan multithreading sajrone pangembangan basa Jawa. Sampeyan bisa maca liyane ing review " Brief Pambuka kanggo Java Thread.yield () ".
Turu - Mudhun asleep thread
Utas bisa uga turu sajrone eksekusi. Iki minangka jinis interaksi sing paling gampang karo benang liyane. Sistem operasi ing ngendi mesin virtual Java diinstal, ing ngendi kode Java dieksekusi, duwe jadwal thread dhewe, sing diarani Thread Scheduler. Iku kang nemtokaken thread kanggo mbukak nalika. Programmer ora bisa sesambungan karo penjadwal iki langsung saka kode Jawa, nanging bisa, liwat JVM, njaluk panjadwal kanggo ngaso thread kanggo sawetara wektu, kanggo "dilebokake kanggo turu." Sampeyan bisa maca liyane ing artikel " Thread.sleep () "Lan" Carane Multithreading dianggo ". Menapa malih, sampeyan bisa mangerteni carane Utas bisa digunakake ing Windows OS: " Internals Windows Thread ". Saiki kita bakal weruh karo mripat kita dhewe. Ayo simpen kode ing ngisor iki menyang fileHelloWorldApp.java
:
class HelloWorldApp {
public static void main(String []args) {
Runnable task = () -> {
try {
int secToWait = 1000 * 60;
Thread.currentThread().sleep(secToWait);
System.out.println("Waked up");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Nalika sampeyan bisa ndeleng, kita duwe tugas sing ngenteni 60 detik, sawise program rampung. Kita ngumpulake javac HelloWorldApp.java
lan mbukak java HelloWorldApp
. Iku luwih apik kanggo miwiti ing jendhela kapisah. Contone, ing Windows bakal kaya iki: start java HelloWorldApp
. Nggunakake printah jps, kita ngerteni PID proses kasebut lan mbukak dhaptar benang kanthi nggunakake jvisualvm --openpid pidПроцесса
: Kaya sing sampeyan ngerteni, benang kita wis mlebu status Turu. Nyatane, turu ing thread saiki bisa ditindakake kanthi luwih apik:
try {
TimeUnit.SECONDS.sleep(60);
System.out.println("Waked up");
} catch (InterruptedException e) {
e.printStackTrace();
}
Sampeyan mbokmenawa wis ngelingi yen kita proses nang endi wae InterruptedException
? Ayo padha ngerti sebabe.
Interrupting thread utawa Thread.interrupting
Masalahe yaiku nalika benang ngenteni turu, ana sing pengin ngganggu ngenteni iki. Ing kasus iki, kita nangani pangecualian kasebut. Iki ditindakake sawisé cara kasebutThread.stop
diumumake Deprecated, i.e. outdated lan undesirable kanggo nggunakake. Alesan kanggo iki yaiku nalika metode kasebut diarani, stop
benang kasebut mung "mateni", sing ora bisa ditebak. Kita ora bisa ngerti kapan aliran bakal mandheg, kita ora bisa njamin konsistensi data. Mbayangno yen sampeyan nulis data menyang file banjur stream kasebut dirusak. Mulane, padha mutusaké sing bakal luwih logis ora kanggo mateni aliran, nanging kanggo ngandhani iku kudu diselani. Carane nanggepi iki gumantung ing aliran dhewe. Rincian liyane bisa ditemokake ing Oracle " Kenapa Thread.stop ora digunakake? " Ayo ndeleng conto:
public static void main(String []args) {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
Ing conto iki, kita ora bakal ngenteni 60 detik, nanging bakal langsung nyithak 'Interrupted'. Iki amarga kita disebut cara thread interrupt
. Cara iki nyetel "gendera internal sing diarani status interupsi". Tegese, saben thread duwe gendera internal sing ora bisa diakses langsung. Nanging kita duwe cara asli kanggo sesambungan karo gendera iki. Nanging iki ora mung cara. Utas bisa uga ana ing proses eksekusi, ora ngenteni apa-apa, nanging mung nindakake tumindak. Nanging bisa nyedhiyakake manawa dheweke pengin ngrampungake ing titik tartamtu ing karyane. Tuladhane:
public static void main(String []args) {
Runnable task = () -> {
while(!Thread.currentThread().isInterrupted()) {
//Do some work
}
System.out.println("Finished");
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
Ing conto ing ndhuwur, sampeyan bisa ndeleng sing daur ulang while
bakal mbukak nganti thread diselani externally. Sing penting kanggo ngerti babagan gendera isInterrupted yaiku yen kita nyekel InterruptedException
, bendera kasebut isInterrupted
direset, banjur isInterrupted
bakal bali palsu. Ana uga cara statis ing kelas Utas sing mung ditrapake kanggo Utas saiki - Thread.interrupted () , nanging cara iki ngreset flag palsu! Sampeyan bisa maca liyane ing bab " Thread Interruption ".
Gabung - Nunggu thread liyane rampung
Jinis ngenteni sing paling gampang yaiku ngenteni thread liyane rampung.public static void main(String []args) throws InterruptedException {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.join();
System.out.println("Finished");
}
Ing conto iki, thread anyar bakal turu kanggo 5 detik. Ing wektu sing padha, benang utama bakal ngenteni nganti benang turu tangi lan rampung karyane. Yen sampeyan ndeleng liwat JVisualVM, kahanan thread bakal katon kaya iki: Thanks kanggo alat ngawasi, sampeyan bisa ndeleng apa sing kedadeyan karo benang kasebut. Cara kasebut join
cukup prasaja, amarga mung cara kanthi kode java sing dieksekusi wait
nalika benang sing diarani urip. Sawise thread mati (ing mandap), Enteni wis mungkasi. Iku kabeh keajaiban saka metode join
. Mulane, ayo pindhah menyang bagean sing paling menarik.
Monitor Konsep
Ing multithreading ana sing kaya Monitor. Umumé, tembung monitor diterjemahaké saka basa Latin minangka "pengawas" utawa "pengawas". Ing kerangka artikel iki, kita bakal nyoba ngelingi inti, lan kanggo sing pengin, aku njaluk sampeyan nyilem menyang materi saka pranala kanggo rincian. Ayo miwiti lelampahan kanthi spesifikasi basa Jawa, yaiku JLS: " 17.1. Sinkronisasi ". Iku ngandika ing ngisor iki: Pranyata metu sing kanggo tujuan sinkronisasi antarane Utas, Jawa nggunakake mekanisme tartamtu disebut "Monitor". Saben obyek duwe monitor sing ana gandhengane, lan benang bisa ngunci utawa mbukak kunci. Sabanjure, kita bakal nemokake tutorial latihan ing situs web Oracle: " Kunci lan Sinkronisasi Intrinsik ". Tutorial iki nerangake yen sinkronisasi ing Jawa dibangun ing babagan entitas internal sing dikenal minangka kunci intrinsik utawa kunci monitor. Asring kunci kuwi mung disebut "monitor". Kita uga ndeleng maneh manawa saben obyek ing Jawa duwe kunci intrinsik sing ana gandhengane. Sampeyan bisa maca " Jawa - Kunci Intrinsik lan Sinkronisasi ". Sabanjure, penting kanggo mangerteni carane obyek ing Jawa bisa digandhengake karo monitor. Saben obyek ing Jawa duwe header - jinis metadata internal sing ora kasedhiya kanggo programmer saka kode, nanging mesin virtual kudu bisa karo obyek bener. Header obyek kalebu MarkWord sing katon kaya iki:https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf
public class HelloWorld{
public static void main(String []args){
Object object = new Object();
synchronized(object) {
System.out.println("Hello World");
}
}
}
Dadi, nggunakake tembung kunci, synchronized
utas saiki (ing baris kode kasebut dieksekusi) nyoba nggunakake monitor sing ana gandhengane karo obyek kasebut object
lan "njaluk kunci" utawa "nangkep monitor" (pilihan kapindho malah luwih disenengi). Yen ora ana pratelan kanggo monitor (yaiku ora ana wong liya sing pengin nyinkronake ing obyek sing padha), Jawa bisa nyoba nindakake optimasi sing diarani "pengunci bias". Judhul obyek ing Mark Word bakal ngemot tag sing cocog lan cathetan sing dipasang ing monitor. Iki nyuda overhead nalika njupuk monitor. Yen monitor wis disambungake menyang thread liyane sadurunge, banjur ngunci iki ora cukup. JVM ngalih menyang jinis ngunci sabanjuré - ngunci dhasar. Iki nggunakake operasi comparison-and-swap (CAS). Ing wektu sing padha, header ing Mark Word ora nyimpen Mark Word maneh, nanging link menyang panyimpenan + tag diganti supaya JVM ngerti yen kita nggunakake kunci dhasar. Yen ana pratelan kanggo monitor sawetara utas (siji wis dijupuk monitor, lan kaloro nunggu monitor dirilis), banjur tag ing Mark Word diganti, lan Mark Word wiwit nyimpen referensi kanggo monitor minangka obyek - sawetara entitas internal JVM. Kaya kasebut ing JEP, ing kasus iki, spasi dibutuhake ing area memori Native Heap kanggo nyimpen entitas iki. Link menyang lokasi panyimpenan entitas internal iki bakal ana ing obyek Mark Word. Mangkono, kaya sing kita deleng, monitor pancen minangka mekanisme kanggo mesthekake sinkronisasi akses pirang-pirang benang menyang sumber daya sing dienggo bareng. Ana sawetara implementasine saka mekanisme iki sing JVM ngalih antarane. Mulane, kanggo kesederhanaan, nalika ngomong babagan monitor, kita bener-bener ngomong babagan kunci.
Disinkronake lan ngenteni kanthi ngunci
Konsep monitor, kaya sing kita deleng sadurunge, ana hubungane karo konsep "pamblokiran sinkronisasi" (utawa, sing uga disebut, bagean kritis). Ayo ndeleng conto:public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Runnable task = () -> {
synchronized (lock) {
System.out.println("thread");
}
};
Thread th1 = new Thread(task);
th1.start();
synchronized (lock) {
for (int i = 0; i < 8; i++) {
Thread.currentThread().sleep(1000);
System.out.print(" " + i);
}
System.out.println(" ...");
}
}
Ing kene, utas utama ngirim tugas menyang utas anyar, banjur langsung "nangkep" kunci kasebut lan nindakake operasi sing dawa (8 detik). Kabeh wektu iki, tugas ora bisa mlebu blok kanggo eksekusi synchronized
, amarga kunci wis dikuwasani. Yen benang ora bisa entuk kunci, bakal ngenteni ing monitor. Sanalika ditampa, bakal terus eksekusi. Nalika thread ninggalake monitor, iku ngeculake kunci. Ing JVisualVM bakal katon kaya iki: Nalika sampeyan bisa ndeleng, status ing JVisualVM diarani "Monitor" amarga thread diblokir lan ora bisa manggoni monitor. Sampeyan uga bisa ngerteni negara thread ing kode, nanging jeneng negara iki ora pas karo istilah JVisualVM, sanajan padha. Ing kasus iki, th1.getState()
daur ulang for
bakal bali BLOCKED , amarga Nalika daur ulang mlaku, monitor lock
dikuwasani main
dening benang, lan benang th1
diblokir lan ora bisa terus digunakake nganti kunci bali. Saliyane pamblokiran sinkronisasi, kabeh cara bisa disinkronake. Contone, metode saka kelas HashTable
:
public synchronized int size() {
return count;
}
Ing siji unit wektu, cara iki bakal dileksanakake mung siji thread. Nanging kita butuh kunci, ta? Ya aku butuh. Ing kasus metode obyek, kunci bakal this
. Ana diskusi menarik babagan topik iki: " Apa ana kauntungan kanggo nggunakake Metode Sinkronisasi tinimbang Blok sing Disinkronake? ". Yen cara statis, banjur kunci ora bakal this
(amarga kanggo cara statis ora bisa this
), nanging obyek kelas (Contone, Integer.class
).
Ngenteni lan ngenteni ing monitor. Cara notifikasi lan notifyAll
Utas duwe cara ngenteni liyane, sing disambungake menyang monitor. Boten kadossleep
lan join
, iku ora bisa mung disebut. Lan jenenge wait
. Cara kasebut dieksekusi wait
ing obyek ing monitor sing pengin ngenteni. Ayo ndeleng conto:
public static void main(String []args) throws InterruptedException {
Object lock = new Object();
// task будет ждать, пока его не оповестят через lock
Runnable task = () -> {
synchronized(lock) {
try {
lock.wait();
} catch(InterruptedException e) {
System.out.println("interrupted");
}
}
// После оповещения нас мы будем ждать, пока сможем взять лок
System.out.println("thread");
};
Thread taskThread = new Thread(task);
taskThread.start();
// Ждём и после этого забираем себе лок, оповещаем и отдаём лок
Thread.currentThread().sleep(3000);
System.out.println("main");
synchronized(lock) {
lock.notify();
}
}
Ing JVisualVM bakal katon kaya iki: Kanggo ngerti cara kerjane, sampeyan kudu ngelingi yen metode wait
kasebut . Iku misale jek aneh sing thread related cara ing . Nanging ing kene ana jawabane. Kaya sing kita eling, saben obyek ing Jawa nduweni header. Header ngemot macem-macem informasi layanan, kalebu informasi babagan monitor-data babagan status ngunci. Lan kita elinga, saben obyek (yaiku saben conto) duwe asosiasi karo entitas JVM internal disebut kunci intrinsik, kang uga disebut monitor. Ing conto ing ndhuwur, tugas nerangake yen kita ngetik blok sinkronisasi ing monitor sing digandhengake karo . Yen bisa entuk kunci ing monitor iki, banjur . Utas sing nindakake tugas iki bakal ngeculake monitor , nanging bakal gabung karo antrian utas sing nunggu kabar ing monitor . Antrian utas iki diarani WAIT-SET, sing luwih bener nggambarake inti. Iku luwih saka pesawat saka antrian. Utas nggawe utas anyar kanthi tugas tugas, diwiwiti lan ngenteni 3 detik. Iki ngidini, kanthi kemungkinan dhuwur, benang anyar kanggo nyekel kunci sadurunge benang lan antri ing monitor. Sawise benang kasebut mlebu blok sinkronisasi lan nindakake kabar babagan benang ing monitor. Sawise kabar dikirim, thread ngeculake monitor , lan thread anyar (sing sadurunge ngenteni) terus dieksekusi sawise ngenteni monitor dirilis. Sampeyan bisa ngirim kabar mung siji saka utas ( ) utawa kabeh utas ing antrian bebarengan ( ). Sampeyan bisa maca liyane ing " Bedane antarane notify () lan notifyAll () ing Jawa ". Wigati dimangerteni manawa pesenan kabar gumantung saka implementasi JVM. Sampeyan bisa maca liyane ing " Carane ngatasi keluwen karo notify lan notifyall? ". Sinkronisasi bisa ditindakake tanpa nemtokake obyek. Iki bisa ditindakake nalika ora bagean kode sing kapisah disinkronake, nanging kabeh cara. Contone, kanggo cara statis kunci bakal obyek kelas (dipikolehi liwat ): notify
java.lang.Object
Object
lock
wait
lock
lock
main
main
main
lock
main
lock
lock
notify
notifyAll
.class
public static synchronized void printA() {
System.out.println("A");
}
public static void printB() {
synchronized(HelloWorld.class) {
System.out.println("B");
}
}
Ing babagan nggunakake kunci, loro cara kasebut padha. Yen cara kasebut ora statis, banjur sinkronisasi bakal ditindakake miturut arus instance
, yaiku, miturut this
. Miturut cara, sadurunge kita ngomong yen nggunakake cara getState
sampeyan bisa njaluk status thread. Dadi ing kene ana thread sing diantrekake dening monitor, status bakal WAITING utawa TIMED_WAITING yen metode kasebut wait
nemtokake watesan wektu tunggu.
Siklus urip saka thread
Kaya sing wis dingerteni, aliran kasebut ngganti statuse sajrone urip. Intine, owah-owahan kasebut minangka siklus urip benang. Nalika thread mung digawe, wis status NEW. Ing posisi iki, durung diwiwiti lan Java Thread Scheduler durung ngerti apa-apa bab thread anyar. Supaya penjadwal thread ngerti babagan thread, sampeyan kudu nelponthread.start()
. Banjur thread bakal pindhah menyang negara RUNNABLE. Ana akeh skema sing ora bener ing Internet ing ngendi negara Runnable lan Running dipisahake. Nanging iki salah, amarga ... Jawa ora mbedakake status "siap mlaku" lan "mlaku". Nalika thread urip nanging ora aktif (ora Runnable), ana ing salah siji saka rong negara:
- BLOCKED - ngenteni entri menyang bagean sing dilindhungi, i.e. menyang
synchonized
blok. - WAITING - ngenteni thread liyane adhedhasar kondisi. Yen kondisi kasebut bener, panjadwal thread bakal miwiti thread.
getState
. Utas uga duwe cara isAlive
sing ngasilake bener yen benang ora Diakhiri.
LockSupport lan parkir Utas
Wiwit Java 1.6 ana mekanisme menarik sing diarani LockSupport . Kelas iki nggandhengake "ijin" utawa ijin karo saben utas sing nggunakake. Telpon carapark
bali langsung yen ijin kasedhiya, manggoni ijin sing padha sak nelpon. Yen ora, diblokir. Nelpon cara unpark
nggawe ijin kasedhiya yen durung kasedhiya. Ijin mung ana 1. Ing Java API , LockSupport
. Semaphore
Ayo katon ing conto prasaja:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(0);
try {
semaphore.acquire();
} catch (InterruptedException e) {
// Просим разрешение и ждём, пока не получим его
e.printStackTrace();
}
System.out.println("Hello, World!");
}
}
Kode iki bakal ngenteni selawase amarga semaphore saiki duwe 0 ijin. Lan nalika diarani kode acquire
(yaiku, njaluk ijin), benang ngenteni nganti entuk ijin. Amarga kita ngenteni, kita kudu ngolah InterruptedException
. Apike, semaphore ngetrapake negara benang sing kapisah. Yen kita katon ing JVisualVM, kita bakal weruh sing negara kita ora Enteni, nanging Park. Ayo ndeleng conto liyane:
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
//Запаркуем текущий поток
System.err.println("Will be Parked");
LockSupport.park();
// Как только нас распаркуют - начнём действовать
System.err.println("Unparked");
};
Thread th = new Thread(task);
th.start();
Thread.currentThread().sleep(2000);
System.err.println("Thread state: " + th.getState());
LockSupport.unpark(th);
Thread.currentThread().sleep(2000);
}
Status thread bakal NUNGGU, nanging JVisualVM mbedakake wait
antarane saka synchronized
lan park
saka LockSupport
. Kenapa iki penting banget LockSupport
? Ayo bali menyang Java API lan deleng Thread State WAITING . Nalika sampeyan bisa ndeleng, mung ana telung cara kanggo njaluk menyang. 2 cara - iki wait
lan join
. Lan sing katelu yaiku LockSupport
. Kunci ing Jawa dibangun kanthi prinsip sing padha LockSupport
lan makili piranti sing luwih dhuwur. Ayo dadi nyoba kanggo nggunakake siji. Ayo katon, contone, ing ReentrantLock
:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{
public static void main(String []args) throws InterruptedException {
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
System.out.println("Thread");
lock.unlock();
};
lock.lock();
Thread th = new Thread(task);
th.start();
System.out.println("main");
Thread.currentThread().sleep(2000);
lock.unlock();
}
}
Kaya ing conto sadurunge, kabeh prasaja ing kene. lock
ngenteni wong ngeculake sumber. Yen kita katon ing JVisualVM, kita bakal weruh yen thread anyar bakal diparkir nganti main
thread menehi kunci. Sampeyan bisa maca liyane babagan kunci kene: " Pemrograman multithreaded ing Jawa 8. Bagean loro. Nyelarasake akses menyang obyek sing bisa diowahi " lan " Java Lock API. Teori lan conto panggunaan ." Kanggo luwih ngerti implementasine kunci, migunani kanggo maca babagan Phazer ing ringkesan " Kelas Phaser ". Lan ngomong babagan macem-macem sinkronisasi, sampeyan kudu maca artikel ing Habré " Java.util.concurrent.* Synchronizers Reference ".
Total
Ing review iki, kita ndeleng cara utama utas sesambungan ing Jawa. Materi tambahan:- Monitor - Gagasan Dasar Sinkronisasi Jawa
- Referensi sinkronisasi java.util.concurrent.*
- Jawaban kanggo pitakonan babagan multithreading ing wawancara
GO TO FULL VERSION