CodeGym /Java Blog /Random /Mas mahusay na magkasama: Java at ang klase ng Thread. Ba...
John Squirrels
Antas
San Francisco

Mas mahusay na magkasama: Java at ang klase ng Thread. Bahagi II — Pag-synchronize

Nai-publish sa grupo

Panimula

Kaya, alam namin na ang Java ay may mga thread. Mababasa mo iyon sa pagsusuri na pinamagatang Better together: Java and the Thread class. Bahagi I — Mga Thread ng pagpapatupad . Ang mga thread ay kinakailangan upang maisagawa ang trabaho nang magkatulad. Dahil dito, malaki ang posibilidad na ang mga thread ay makikipag-ugnayan sa isa't isa. Tingnan natin kung paano ito nangyayari at kung anong mga pangunahing tool ang mayroon tayo. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 1

Magbigay

Ang Thread.yield() ay nakakalito at bihirang gamitin. Ito ay inilarawan sa maraming iba't ibang paraan sa Internet. Kabilang ang ilang tao na nagsusulat na mayroong ilang pila ng mga thread, kung saan bababa ang isang thread batay sa mga priyoridad ng thread. Isinulat ng ibang tao na babaguhin ng isang thread ang katayuan nito mula sa "Tumatakbo" patungo sa "Mapapatakbo" (kahit na walang pagkakaiba sa pagitan ng mga katayuang ito, ibig sabihin, hindi nakikilala ng Java ang mga ito). Ang katotohanan ay ang lahat ng ito ay hindi gaanong kilala ngunit mas simple sa isang kahulugan. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 2Mayroong isang bug ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) na naka-log para sa yield()dokumentasyon ng pamamaraan. Kung babasahin mo ito, malinaw na angyield()Ang pamamaraan ay talagang nagbibigay lamang ng ilang rekomendasyon sa Java thread scheduler na ang thread na ito ay maaaring bigyan ng mas kaunting oras ng pagpapatupad. Ngunit kung ano ang aktwal na nangyayari, ibig sabihin, kung ang scheduler ay kumikilos sa rekomendasyon at kung ano ang ginagawa nito sa pangkalahatan, ay depende sa pagpapatupad ng JVM at sa operating system. At maaaring depende rin ito sa ilang iba pang mga kadahilanan. Ang lahat ng pagkalito ay malamang dahil sa katotohanan na ang multithreading ay muling naisip habang ang wikang Java ay nabuo. Magbasa nang higit pa sa pangkalahatang-ideya dito: Maikling Panimula sa Java Thread.yield() .

Matulog

Ang isang thread ay maaaring matulog sa panahon ng pagpapatupad nito. Ito ang pinakamadaling uri ng pakikipag-ugnayan sa ibang mga thread. Ang operating system na nagpapatakbo ng Java virtual machine na nagpapatakbo ng aming Java code ay may sariling thread scheduler . Ito ang magpapasya kung aling thread ang magsisimula at kailan. Ang isang programmer ay hindi maaaring makipag-ugnayan sa scheduler na ito nang direkta mula sa Java code, sa pamamagitan lamang ng JVM. Maaari niyang hilingin sa scheduler na i-pause sandali ang thread, ibig sabihin, patulugin ito. Maaari kang magbasa ng higit pa sa mga artikulong ito: Thread.sleep() at Paano gumagana ang Multithreading . Maaari mo ring tingnan kung paano gumagana ang mga thread sa mga operating system ng Windows: Mga Internal ng Windows Thread . At ngayon tingnan natin ito ng ating sariling mga mata. I-save ang sumusunod na code sa isang file na pinangalanang HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Tulad ng nakikita mo, mayroon kaming ilang gawain na naghihintay ng 60 segundo, pagkatapos nito magtatapos ang programa. Nag-compile kami gamit ang command na " javac HelloWorldApp.java" at pagkatapos ay patakbuhin ang program gamit ang " java HelloWorldApp". Pinakamainam na simulan ang programa sa isang hiwalay na window. Halimbawa, sa Windows, ito ay ganito: start java HelloWorldApp. Ginagamit namin ang jps command para makuha ang PID (process ID), at binubuksan namin ang listahan ng mga thread na may " jvisualvm --openpid pid: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 3Gaya ng nakikita mo, ang aming thread ay mayroon na ngayong "Sleeping" status. Sa katunayan, may mas eleganteng paraan para makatulong ang aming thread ay may matamis na pangarap:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Napansin mo ba na kami ay humahawak InterruptedExceptionsa lahat ng dako? Intindihin natin kung bakit.

Thread.interrupt()

Ang bagay ay habang naghihintay/natutulog ang isang thread, maaaring may gustong humarang. Sa kasong ito, pinangangasiwaan namin ang isang InterruptedException. Nalikha ang mekanismong ito pagkatapos Thread.stop()ideklarang Hindi na ginagamit ang pamamaraan, ibig sabihin, lipas na at hindi kanais-nais. Ang dahilan ay kapag stop()tinawag ang pamamaraan, ang thread ay "pinatay" lamang, na napaka-unpredictable. Hindi namin alam kung kailan ititigil ang thread, at hindi namin magagarantiya ang pagkakapare-pareho ng data. Isipin na nagsusulat ka ng data sa isang file habang pinapatay ang thread. Sa halip na patayin ang thread, nagpasya ang mga tagalikha ng Java na mas makatuwirang sabihin dito na dapat itong maantala. Kung paano tumugon sa impormasyong ito ay isang bagay para sa thread mismo ang magpasya. Para sa higit pang mga detalye, basahin ang Bakit hindi na ginagamit ang Thread.stop?sa website ng Oracle. Tingnan natin ang isang halimbawa:

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();
}
Sa halimbawang ito, hindi kami maghihintay ng 60 segundo. Sa halip, agad naming ipapakita ang "Nagambala." Ito ay dahil tinawag namin ang interrupt()pamamaraan sa thread. Ang pamamaraang ito ay nagtatakda ng panloob na bandila na tinatawag na "interrupt status". Ibig sabihin, ang bawat thread ay may panloob na bandila na hindi direktang naa-access. Ngunit mayroon kaming mga katutubong pamamaraan para sa pakikipag-ugnayan sa watawat na ito. Ngunit hindi iyon ang tanging paraan. Maaaring tumatakbo ang isang thread, hindi naghihintay ng isang bagay, nagsasagawa lamang ng mga aksyon. Ngunit maaaring asahan nito na nais ng iba na tapusin ang gawain nito sa isang partikular na oras. Halimbawa:

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();
}
Sa halimbawa sa itaas, ang whileloop ay isasagawa hanggang sa ang thread ay magambala sa labas. Tungkol naman sa isInterruptedflag, mahalagang malaman na kung mahuli natin ang isang InterruptedException, mare-reset ang isInterrupted na flag, at pagkatapos isInterrupted()ay magbabalik ng false. Ang klase ng Thread ay mayroon ding static na Thread.interrupted() na pamamaraan na nalalapat lamang sa kasalukuyang thread, ngunit ni-reset ng pamamaraang ito ang flag sa false! Magbasa nang higit pa sa kabanatang ito na pinamagatang Thread Interruption .

Sumali (Hintaying matapos ang isa pang thread)

Ang pinakasimpleng uri ng paghihintay ay naghihintay na matapos ang isa pang thread.

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");
}
Sa halimbawang ito, matutulog ang bagong thread ng 5 segundo. Kasabay nito, maghihintay ang pangunahing thread hanggang sa magising ang natutulog na thread at matapos ang trabaho nito. Kung titingnan mo ang estado ng thread sa JVisualVM, magiging ganito ito: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 4Salamat sa mga tool sa pagsubaybay, makikita mo kung ano ang nangyayari sa thread. Ang joinpamamaraan ay medyo simple, dahil ito ay isang pamamaraan lamang na may Java code na nagpapatupad wait()hangga't ang thread kung saan ito tinatawag ay buhay. Sa sandaling mamatay ang thread (kapag natapos na ito sa trabaho nito), ang paghihintay ay nagambala. At iyon ang lahat ng magic ng join()pamamaraan. Kaya, lumipat tayo sa pinaka-kagiliw-giliw na bagay.

Subaybayan

Kasama sa multithreading ang konsepto ng isang monitor. Ang salitang monitor ay dumating sa Ingles sa pamamagitan ng ika-16 na siglo na Latin at nangangahulugang "isang instrumento o aparato na ginagamit para sa pagmamasid, pagsuri, o pagpapanatili ng tuluy-tuloy na talaan ng isang proseso". Sa konteksto ng artikulong ito, susubukan naming saklawin ang mga pangunahing kaalaman. Para sa sinumang nais ng mga detalye, mangyaring sumisid sa mga naka-link na materyales. Sinimulan namin ang aming paglalakbay gamit ang Java Language Specification (JLS): 17.1. Pag-synchronize . Sinasabi nito ang sumusunod: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 5Lumalabas na ang Java ay gumagamit ng isang "monitor" na mekanismo para sa pag-synchronize sa pagitan ng mga thread. Ang isang monitor ay nauugnay sa bawat bagay, at maaaring makuha ito ng mga thread gamit lock()o ilabas ito gamit ang unlock(). Susunod, makikita natin ang tutorial sa website ng Oracle: Intrinsic Locks and Synchronization. Sinasabi ng tutorial na ito na ang pag-synchronize ng Java ay binuo sa paligid ng isang panloob na entity na tinatawag na intrinsic lock o monitor lock . Ang lock na ito ay kadalasang tinatawag na " monitor ". Muli nating nakikita na ang bawat bagay sa Java ay may intrinsic lock na nauugnay dito. Mababasa mo ang Java - Intrinsic Locks and Synchronization . Susunod, mahalagang maunawaan kung paano maiuugnay ang isang bagay sa Java sa isang monitor. Sa Java, ang bawat bagay ay may header na nag-iimbak ng panloob na metadata na hindi available sa programmer mula sa code, ngunit kailangan ng virtual machine na gumana nang tama sa mga bagay. Ang header ng object ay may kasamang "markang salita", na ganito ang hitsura: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Narito ang isang artikulo ng JavaWorld na lubhang kapaki-pakinabang: Paano gumaganap ang Java virtual machine ng thread synchronization . Dapat isama ang artikulong ito sa paglalarawan mula sa seksyong "Buod" ng sumusunod na isyu sa JDK bug-tracking system: JDK-8183909 . Mababasa mo ang parehong bagay dito: JEP-8183909 . Kaya, sa Java, ang isang monitor ay nauugnay sa isang bagay at ginagamit upang harangan ang isang thread kapag sinubukan ng thread na makuha (o makuha) ang lock. Narito ang pinakasimpleng halimbawa:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Dito, ang kasalukuyang thread (ang isa kung saan ang mga linya ng code na ito ay pinaandar) ay gumagamit ng synchronizedkeyword upang subukang gamitin ang monitor na nauugnay saobject"\variable para makuha/makuha ang lock. Kung walang ibang nakikipaglaban para sa monitor (ibig sabihin, walang ibang nagpapatakbo ng naka-synchronize na code gamit ang parehong bagay), maaaring subukan ng Java na magsagawa ng isang optimization na tinatawag na "biased locking". Ang isang nauugnay na tag at isang talaan tungkol sa kung aling thread ang nagmamay-ari ng lock ng monitor ay idinaragdag sa mark word sa header ng object. Binabawasan nito ang overhead na kinakailangan upang i-lock ang isang monitor. Kung ang monitor ay dating pagmamay-ari ng isa pang thread, kung gayon ang naturang pag-lock ay hindi sapat. Lumipat ang JVM sa susunod na uri ng pag-lock: "basic locking". Gumagamit ito ng mga pagpapatakbo ng compare-and-swap (CAS). Higit pa rito, ang mark word mismo ng object header ay hindi na nag-iimbak ng mark word, sa halip ay isang reference sa kung saan ito nakaimbak, at nagbabago ang tag upang maunawaan ng JVM na gumagamit kami ng basic na locking. Kung maraming mga thread ang nakikipagkumpitensya (naglalaban) para sa isang monitor (nakuha ng isa ang lock, at ang isang segundo ay naghihintay na mailabas ang lock), pagkatapos ay ang tag sa mark word ay nagbabago, at ang mark word ay nag-iimbak ngayon ng isang reference sa monitor bilang isang bagay - ilang panloob na entity ng JVM. Gaya ng nakasaad sa JDK Enchancement Proposal (JEP), ang sitwasyong ito ay nangangailangan ng espasyo sa Native Heap na lugar ng memorya upang maiimbak ang entity na ito. Ang reference sa lokasyon ng memorya ng panloob na entity na ito ay maiimbak sa mark word ng object header. Kaya, ang isang monitor ay talagang isang mekanismo para sa pag-synchronize ng access sa mga nakabahaging mapagkukunan sa maraming mga thread. Ang JVM ay lumilipat sa pagitan ng ilang pagpapatupad ng mekanismong ito. Kaya, para sa pagiging simple, kapag pinag-uusapan ang monitor, talagang pinag-uusapan natin ang tungkol sa mga kandado. at isang segundo ang naghihintay na mailabas ang lock), pagkatapos ay nagbabago ang tag sa mark word, at ang mark word ay nag-iimbak ngayon ng reference sa monitor bilang isang object — ilang panloob na entity ng JVM. Gaya ng nakasaad sa JDK Enchancement Proposal (JEP), ang sitwasyong ito ay nangangailangan ng espasyo sa Native Heap na lugar ng memorya upang maiimbak ang entity na ito. Ang reference sa lokasyon ng memorya ng panloob na entity na ito ay maiimbak sa mark word ng object header. Kaya, ang isang monitor ay talagang isang mekanismo para sa pag-synchronize ng access sa mga nakabahaging mapagkukunan sa maraming mga thread. Ang JVM ay lumilipat sa pagitan ng ilang pagpapatupad ng mekanismong ito. Kaya, para sa pagiging simple, kapag pinag-uusapan ang monitor, talagang pinag-uusapan natin ang tungkol sa mga kandado. at isang segundo ang naghihintay na mailabas ang lock), pagkatapos ay nagbabago ang tag sa mark word, at ang mark word ay nag-iimbak ngayon ng reference sa monitor bilang isang object — ilang panloob na entity ng JVM. Gaya ng nakasaad sa JDK Enchancement Proposal (JEP), ang sitwasyong ito ay nangangailangan ng espasyo sa Native Heap na lugar ng memorya upang maiimbak ang entity na ito. Ang reference sa lokasyon ng memorya ng panloob na entity na ito ay maiimbak sa mark word ng object header. Kaya, ang isang monitor ay talagang isang mekanismo para sa pag-synchronize ng access sa mga nakabahaging mapagkukunan sa maraming mga thread. Ang JVM ay lumilipat sa pagitan ng ilang pagpapatupad ng mekanismong ito. Kaya, para sa pagiging simple, kapag pinag-uusapan ang monitor, talagang pinag-uusapan natin ang tungkol sa mga kandado. at ang mark word ay nag-iimbak na ngayon ng reference sa monitor bilang isang object — ilang panloob na entity ng JVM. Gaya ng nakasaad sa JDK Enchancement Proposal (JEP), ang sitwasyong ito ay nangangailangan ng espasyo sa Native Heap na lugar ng memorya upang maiimbak ang entity na ito. Ang reference sa lokasyon ng memorya ng panloob na entity na ito ay maiimbak sa mark word ng object header. Kaya, ang isang monitor ay talagang isang mekanismo para sa pag-synchronize ng access sa mga nakabahaging mapagkukunan sa maraming mga thread. Ang JVM ay lumilipat sa pagitan ng ilang pagpapatupad ng mekanismong ito. Kaya, para sa pagiging simple, kapag pinag-uusapan ang monitor, talagang pinag-uusapan natin ang tungkol sa mga kandado. at ang mark word ay nag-iimbak na ngayon ng reference sa monitor bilang isang object — ilang panloob na entity ng JVM. Gaya ng nakasaad sa JDK Enchancement Proposal (JEP), ang sitwasyong ito ay nangangailangan ng espasyo sa Native Heap na lugar ng memorya upang maiimbak ang entity na ito. Ang reference sa lokasyon ng memorya ng panloob na entity na ito ay maiimbak sa mark word ng object header. Kaya, ang isang monitor ay talagang isang mekanismo para sa pag-synchronize ng access sa mga nakabahaging mapagkukunan sa maraming mga thread. Ang JVM ay lumilipat sa pagitan ng ilang pagpapatupad ng mekanismong ito. Kaya, para sa pagiging simple, kapag pinag-uusapan ang monitor, talagang pinag-uusapan natin ang tungkol sa mga kandado. Ang reference sa lokasyon ng memorya ng panloob na entity na ito ay maiimbak sa mark word ng object header. Kaya, ang isang monitor ay talagang isang mekanismo para sa pag-synchronize ng access sa mga nakabahaging mapagkukunan sa maraming mga thread. Ang JVM ay lumilipat sa pagitan ng ilang pagpapatupad ng mekanismong ito. Kaya, para sa pagiging simple, kapag pinag-uusapan ang monitor, talagang pinag-uusapan natin ang tungkol sa mga kandado. Ang reference sa lokasyon ng memorya ng panloob na entity na ito ay maiimbak sa mark word ng object header. Kaya, ang isang monitor ay talagang isang mekanismo para sa pag-synchronize ng access sa mga nakabahaging mapagkukunan sa maraming mga thread. Ang JVM ay lumilipat sa pagitan ng ilang pagpapatupad ng mekanismong ito. Kaya, para sa pagiging simple, kapag pinag-uusapan ang monitor, talagang pinag-uusapan natin ang tungkol sa mga kandado. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 7

Naka-synchronize (naghihintay ng lock)

Tulad ng nakita natin kanina, ang konsepto ng isang "naka-synchronize na bloke" (o "kritikal na seksyon") ay malapit na nauugnay sa konsepto ng isang monitor. Tingnan ang isang halimbawa:

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(" ...");
	}
}
Dito, ang pangunahing thread ay unang ipinapasa ang bagay ng gawain sa bagong thread, at pagkatapos ay agad na nakuha ang lock at nagsasagawa ng mahabang operasyon kasama nito (8 segundo). Sa lahat ng oras na ito, ang gawain ay hindi maaaring magpatuloy, dahil hindi ito makapasok sa synchronizedbloke, dahil ang lock ay nakuha na. Kung hindi makuha ng thread ang lock, maghihintay ito sa monitor. Sa sandaling makuha nito ang lock, magpapatuloy ito sa pagpapatupad. Kapag lumabas ang isang thread sa isang monitor, inilalabas nito ang lock. Sa JVisualVM, ganito ang hitsura: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 8Gaya ng nakikita mo sa JVisualVM, ang status ay "Monitor", ibig sabihin ay naka-block ang thread at hindi maaaring kunin ang monitor. Maaari mo ring gamitin ang code upang matukoy ang status ng isang thread, ngunit ang mga pangalan ng status na tinutukoy sa ganitong paraan ay hindi tumutugma sa mga pangalan na ginamit sa JVisualVM, kahit na magkapareho ang mga ito. Sa kasong ito, angth1.getState()Ang pahayag sa para sa loop ay babalik BLOCKED , dahil hangga't tumatakbo ang loop, ang lockmonitor ng object ay inookupahan ng mainthread, at ang th1thread ay naharang at hindi maaaring magpatuloy hanggang sa mailabas ang lock. Bilang karagdagan sa mga naka-synchronize na bloke, ang isang buong pamamaraan ay maaaring i-synchronize. Halimbawa, narito ang isang pamamaraan mula sa HashTableklase:

public synchronized int size() {
	return count;
}
Ang pamamaraang ito ay isasagawa lamang ng isang thread sa anumang oras. Kailangan ba talaga natin ang lock? Oo, kailangan namin ito. Sa kaso ng mga pamamaraan ng halimbawa, ang bagay na "ito" (kasalukuyang bagay) ay gumaganap bilang isang lock. Mayroong isang kawili-wiling talakayan sa paksang ito dito: Mayroon bang kalamangan sa paggamit ng Naka-synchronize na Paraan sa halip na isang Naka-synchronize na Block? . Kung ang pamamaraan ay static, ang lock ay hindi ang "ito" na bagay (dahil walang "ito" na bagay para sa isang static na pamamaraan), ngunit sa halip ay isang Class object (halimbawa, ) Integer.class.

Maghintay (naghihintay para sa isang monitor). notify() at notifyAll() na mga pamamaraan

Ang klase ng Thread ay may isa pang paraan ng paghihintay na nauugnay sa isang monitor. Hindi tulad sleep()ng at join(), ang paraang ito ay hindi basta-basta matatawag. Ang pangalan nito ay wait(). Ang waitpamamaraan ay tinatawag sa bagay na nauugnay sa monitor na gusto nating hintayin. Tingnan natin ang isang halimbawa:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
Sa JVisualVM, ganito ang hitsura: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 10Upang maunawaan kung paano ito gumagana, tandaan na ang wait()at notify()mga pamamaraan ay nauugnay sa java.lang.Object. Maaaring mukhang kakaiba na ang mga pamamaraan na nauugnay sa thread ay nasa Objectklase. Ngunit ang dahilan nito ngayon ay nagbubukas. Maaalala mo na ang bawat bagay sa Java ay may isang header. Ang header ay naglalaman ng iba't ibang impormasyon sa housekeeping, kabilang ang impormasyon tungkol sa monitor, ibig sabihin, ang katayuan ng lock. Tandaan, ang bawat object, o instance ng isang klase, ay nauugnay sa isang panloob na entity sa JVM, na tinatawag na intrinsic lock o monitor. Sa halimbawa sa itaas, ang code para sa bagay na gawain ay nagpapahiwatig na ipinasok namin ang naka-synchronize na bloke para sa monitor na nauugnay sa lockbagay. Kung magtagumpay tayo sa pagkuha ng lock para sa monitor na ito, kung gayonwait()ay tinatawag na. Ilalabas ng thread na nagsasagawa ng gawain ang lockmonitor ng object, ngunit papasok sa queue ng mga thread na naghihintay ng notification mula sa lockmonitor ng object. Ang pila ng mga thread na ito ay tinatawag na WAIT SET, na mas maayos na nagpapakita ng layunin nito. Iyon ay, ito ay higit pa sa isang set kaysa sa isang pila. Lumilikha ang mainthread ng bagong thread na may task object, sisimulan ito, at maghihintay ng 3 segundo. Dahil dito, malaki ang posibilidad na makuha ng bagong thread ang lock bago ang mainthread, at makapasok sa queue ng monitor. Pagkatapos nito, ang mainthread mismo ay pumapasok sa locknaka-synchronize na bloke ng object at nagsasagawa ng thread notification gamit ang monitor. Pagkatapos maipadala ang notification, mainilalabas ng thread anglockAng monitor ng object, at ang bagong thread, na dati nang naghihintay para sa lockmonitor ng object na ilabas, ay nagpapatuloy sa pagpapatupad. Posibleng magpadala ng notification sa isang thread lang ( notify()) o sabay-sabay sa lahat ng thread sa queue ( notifyAll()). Magbasa nang higit pa dito: Pagkakaiba sa pagitan ng notify() at notifyAll() sa Java . Mahalagang tandaan na ang pagkakasunud-sunod ng notification ay nakadepende sa kung paano ipinapatupad ang JVM. Magbasa nang higit pa dito: Paano malulutas ang gutom gamit ang notify at notifyAll? . Maaaring isagawa ang pag-synchronize nang hindi tinukoy ang isang bagay. Magagawa mo ito kapag ang isang buong pamamaraan ay naka-synchronize sa halip na isang bloke ng code. Halimbawa, para sa mga static na pamamaraan, ang lock ay magiging Class object (nakuha sa pamamagitan ng .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Sa mga tuntunin ng paggamit ng mga kandado, ang parehong mga pamamaraan ay pareho. Kung ang isang paraan ay hindi static, pagkatapos ay isasagawa ang pag-synchronize gamit ang kasalukuyang instance, iyon ay, gamit ang this. By the way, sinabi namin kanina na pwede mong gamitin ang getState()method para makuha ang status ng isang thread. Halimbawa, para sa isang thread sa queue na naghihintay ng monitor, ang status ay WAITING o TIMED_WAITING, kung ang wait()pamamaraan ay nagtakda ng timeout. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

Ikot ng buhay ng thread

Sa paglipas ng buhay nito, nagbabago ang status ng isang thread. Sa katunayan, binubuo ng mga pagbabagong ito ang ikot ng buhay ng thread. Sa sandaling malikha ang isang thread, BAGO ang status nito. Sa ganitong estado, ang bagong thread ay hindi pa tumatakbo at ang Java thread scheduler ay wala pang alam tungkol dito. Upang matutunan ng thread scheduler ang tungkol sa thread, dapat mong tawagan ang thread.start()pamamaraan. Pagkatapos ang thread ay lilipat sa RUNNABLE na estado. Ang Internet ay maraming maling diagram na nakikilala sa pagitan ng "Runnable" at "Running" na estado. Ngunit ito ay isang pagkakamali, dahil ang Java ay hindi nakikilala sa pagitan ng "ready to work" (runnable) at "working" (running). Kapag ang isang thread ay buhay ngunit hindi aktibo (hindi Runnable), ito ay nasa isa sa dalawang estado:
  • BLOCKED — naghihintay na pumasok sa isang kritikal na seksyon, ibig sabihin, isang synchronizedbloke.
  • NAGHIHINTAY — naghihintay ng isa pang thread upang matugunan ang ilang kundisyon.
Kung nasiyahan ang kundisyon, sisimulan ng thread scheduler ang thread. Kung ang thread ay naghihintay hanggang sa isang tinukoy na oras, ang status nito ay TIMED_WAITING. Kung ang thread ay hindi na tumatakbo (ito ay tapos na o ang isang pagbubukod ay itinapon), pagkatapos ay papasok ito sa status na TERMINATED. Upang malaman ang estado ng isang thread, gamitin ang getState()pamamaraan. Ang mga thread ay mayroon ding isAlive()pamamaraan, na nagbabalik ng totoo kung ang thread ay hindi TINATAPOS.

LockSupport at paradahan ng thread

Simula sa Java 1.6, lumitaw ang isang kawili-wiling mekanismo na tinatawag na LockSupport . Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 12Ang klase na ito ay nag-uugnay ng "permit" sa bawat thread na gumagamit nito. Ang isang tawag sa park()pamamaraan ay babalik kaagad kung ang permit ay magagamit, na ginagamit ang permit sa proseso. Kung hindi, ito ay humaharang. Ang pagtawag sa unparkpamamaraan ay ginagawang available ang permit kung hindi pa ito magagamit. May 1 permit lang. Ang dokumentasyon ng Java para sa LockSupportay tumutukoy sa Semaphoreklase. Tingnan natin ang isang simpleng halimbawa:

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) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Ang code na ito ay palaging maghihintay, dahil ngayon ang semaphore ay may 0 permit. At kapag acquire()tinawag sa code (ibig sabihin, humiling ng permit), naghihintay ang thread hanggang sa matanggap nito ang permit. Dahil naghihintay tayo, kailangan nating hawakan InterruptedException. Kapansin-pansin, ang semaphore ay nakakakuha ng isang hiwalay na estado ng thread. Kung titingnan natin sa JVisualVM, makikita natin na ang estado ay hindi "Maghintay", ngunit "Park". Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi II — Pag-synchronize - 13Tingnan natin ang isa pang halimbawa:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            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);
}
Ang status ng thread ay MAGHIHINTAY, ngunit ang JVisualVM ay nakikilala sa pagitan waitng synchronizedkeyword at parkmula sa LockSupportklase. Bakit ito LockSupportnapakahalaga? Muli kaming bumaling sa dokumentasyon ng Java at tumingin sa status ng WAITING thread. Tulad ng nakikita mo, mayroon lamang tatlong paraan upang makapasok dito. Dalawa sa mga paraan na iyon ay wait()at join(). At ang pangatlo ay LockSupport. Sa Java, maaari ding buuin ang mga kandado sa LockSupport at mag-alok ng mga tool sa mas mataas na antas. Subukan nating gumamit ng isa. Halimbawa, tingnan ang 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();
    }
}
Tulad ng sa mga nakaraang halimbawa, ang lahat ay simple dito. Ang lockbagay ay naghihintay para sa isang tao na ilabas ang nakabahaging mapagkukunan. Kung titingnan natin sa JVisualVM, makikita natin na ang bagong thread ay ipaparada hanggang sa mainilabas ng thread ang lock dito. Maaari kang magbasa ng higit pa tungkol sa mga kandado dito: Java 8 StampedLocks kumpara sa ReadWriteLocks at Synchronized and Lock API sa Java. Upang mas maunawaan kung paano ipinapatupad ang mga lock, makatutulong na basahin ang tungkol sa Phaser sa artikulong ito: Gabay sa Java Phaser . At pagsasalita tungkol sa iba't ibang mga synchronizer, dapat mong basahin ang artikulo ng DZone sa The Java Synchronizers.

Konklusyon

Sa pagsusuring ito, sinuri namin ang mga pangunahing paraan ng pakikipag-ugnayan ng mga thread sa Java. Karagdagang materyal: Mas mahusay na magkasama: Java at ang klase ng Thread. Bahagi I — Mga Thread ng execution Mas mahusay na magkasama: Java at ang Thread class. Part III — Mas Mahusay na Pakikipag-ugnayan: Java at ang Thread class. Bahagi IV — Matatawagan, Hinaharap, at mga kaibigan Mas mahusay na magkasama: Java at ang Thread na klase. Part V — Executor, ThreadPool, Fork/Join Better together: Java at ang Thread class. Bahagi VI - Sunog!
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION