Isang maikling pangkalahatang-ideya ng mga detalye kung paano nakikipag-ugnayan ang mga thread. Noong nakaraan, tiningnan namin kung paano naka-synchronize ang mga thread sa isa't isa. Sa pagkakataong ito, susuriin natin ang mga problemang maaaring lumitaw habang nakikipag-ugnayan ang mga thread, at pag-uusapan natin kung paano maiiwasan ang mga ito. Magbibigay din kami ng ilang kapaki-pakinabang na link para sa mas malalim na pag-aaral.
Sa naka-install na JVisualVM plugin (sa pamamagitan ng Tools -> Plugins), makikita natin kung saan naganap ang deadlock:
Ayon sa JVisualVM, nakikita natin ang mga panahon ng pagtulog at isang panahon ng parke (ito ay kapag ang isang thread ay sumusubok na kumuha ng lock — pumapasok ito sa estado ng parke, tulad ng napag-usapan natin kanina nang pag-usapan natin ang tungkol sa pag-synchronize ng thread ) . Makakakita ka ng halimbawa ng livelock dito: Java - Thread Livelock .
Makakakita ka ng sobrang halimbawa dito: Java - Thread Starvation and Fairness . Ipinapakita ng halimbawang ito kung ano ang nangyayari sa mga thread sa panahon ng gutom at kung paano hinahayaan ka ng isang maliit na pagbabago mula sa
Idinagdag ang inspeksyon na ito sa IntelliJ IDEA bilang bahagi ng isyu na IDEA-61117 , na nakalista sa Mga Tala sa Paglabas noong 2010.

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 . At na-explore namin ang katotohanang maaaring mag-synchronize ang mga thread sa isa't isa sa review na pinamagatang Better together: Java and the Thread class. Bahagi II — Pag-synchronize . Oras na para pag-usapan kung paano nakikipag-ugnayan ang mga thread sa isa't isa. Paano sila nagbabahagi ng ibinahaging mapagkukunan? Anong mga problema ang maaaring lumitaw dito?
Deadlock
Ang pinakanakakatakot na problema sa lahat ay deadlock. Ang deadlock ay kapag ang dalawa o higit pang mga thread ay walang hanggang naghihintay para sa isa pa. Kukuha kami ng halimbawa mula sa Oracle webpage na naglalarawan ng deadlock :
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse = new Friend("Alphonse");
final Friend gaston = new Friend("Gaston");
new Thread(() -> alphonse.bow(gaston)).start();
new Thread(() -> gaston.bow(alphonse)).start();
}
}
Maaaring hindi maganap dito ang deadlock sa unang pagkakataon, ngunit kung nag-hang ang iyong program, oras na para tumakbo jvisualvm
: 
"Thread-1" - Thread t@12
java.lang.Thread.State: BLOCKED
at Deadlock$Friend.bowBack(Deadlock.java:16)
- waiting to lock <33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
Naghihintay ang thread 1 para sa lock mula sa thread 0. Bakit nangyayari iyon? Thread-1
magsisimulang tumakbo at isagawa ang Friend#bow
pamamaraan. Ito ay minarkahan ng synchronized
keyword, na nangangahulugang kinukuha namin ang monitor para sa this
(kasalukuyang bagay). Ang input ng pamamaraan ay isang sanggunian sa iba pang Friend
bagay. Ngayon, Thread-1
gustong isagawa ang pamamaraan sa kabilang Friend
, at dapat makuha ang lock nito upang magawa ito. Ngunit kung ang ibang thread (sa kasong ito Thread-0
) ay pinamamahalaang pumasok sa bow()
pamamaraan, kung gayon ang lock ay nakuha na at Thread-1
naghihintay para saThread-0
, at kabaliktaran. Ito ay hindi malulutas, at tinatawag namin itong deadlock. Parang death grip na hindi mabitawan, ang deadlock ay mutual blocking na hindi masisira. Para sa isa pang paliwanag ng deadlock, maaari mong panoorin ang video na ito: Deadlock and Livelock Explained .
Livelock
Kung may deadlock, may livelock din ba? Oo, mayroon :) Nangyayari ang livelock kapag ang mga thread sa panlabas na anyo ay tila buhay, ngunit wala silang magagawa, dahil ang (mga) kundisyon na kinakailangan para sa kanila upang ipagpatuloy ang kanilang trabaho ay hindi maaaring matupad. Karaniwan, ang livelock ay katulad ng deadlock, ngunit ang mga thread ay hindi "nakabit" na naghihintay para sa isang monitor. Sa halip, magpakailanman silang gumagawa ng isang bagay. Halimbawa:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_PURPLE = "\u001B[35m";
public static void log(String text) {
String name = Thread.currentThread().getName(); // Like "Thread-1" or "Thread-0"
String color = ANSI_BLUE;
int val = Integer.valueOf(name.substring(name.lastIndexOf("-") + 1)) + 1;
if (val != 0) {
color = ANSI_PURPLE;
}
System.out.println(color + name + ": " + text + color);
try {
System.out.println(color + name + ": wait for " + val + " sec" + color);
Thread.currentThread().sleep(val * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Lock first = new ReentrantLock();
Lock second = new ReentrantLock();
Runnable locker = () -> {
boolean firstLocked = false;
boolean secondLocked = false;
try {
while (!firstLocked || !secondLocked) {
firstLocked = first.tryLock(100, TimeUnit.MILLISECONDS);
log("First Locked: " + firstLocked);
secondLocked = second.tryLock(100, TimeUnit.MILLISECONDS);
log("Second Locked: " + secondLocked);
}
first.unlock();
second.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(locker).start();
new Thread(locker).start();
}
}
Ang tagumpay ng code na ito ay nakasalalay sa pagkakasunud-sunod kung saan sinisimulan ng Java thread scheduler ang mga thread. Kung Thead-1
magsisimula muna, pagkatapos ay makakakuha tayo ng livelock:
Thread-1: First Locked: true
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
Thread-0: Second Locked: true
Thread-0: wait for 1 sec
Thread-1: Second Locked: false
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
...
Tulad ng nakikita mo mula sa halimbawa, sinusubukan ng parehong mga thread na kunin ang parehong mga kandado, ngunit nabigo sila. Ngunit, hindi sila deadlock. Sa panlabas, maayos ang lahat at ginagawa nila ang kanilang trabaho. 
Pagkagutom
Bilang karagdagan sa deadlock at livelock, may isa pang problema na maaaring mangyari sa panahon ng multithreading: gutom. Ang hindi pangkaraniwang bagay na ito ay naiiba sa mga naunang paraan ng pagharang dahil ang mga thread ay hindi naka-block — wala silang sapat na mapagkukunan. Bilang resulta, habang ang ilang mga thread ay tumatagal ng lahat ng oras ng pagpapatupad, ang iba ay hindi maaaring tumakbo:
https://www.logicbig.com/
Thread.sleep()
upang maipamahagi ang load nang pantay-pantay.Thread.wait()

Mga kondisyon ng lahi
Sa multithreading, mayroong isang bagay bilang isang "kondisyon ng lahi". Ang hindi pangkaraniwang bagay na ito ay nangyayari kapag ang mga thread ay nagbabahagi ng isang mapagkukunan, ngunit ang code ay isinulat sa paraang hindi nito tinitiyak ang tamang pagbabahagi. Tingnan ang isang halimbawa:
public class App {
public static int value = 0;
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
int oldValue = value;
int newValue = ++value;
if (oldValue + 1 != newValue) {
throw new IllegalStateException(oldValue + " + 1 = " + newValue);
}
}
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
Maaaring hindi makabuo ng error ang code na ito sa unang pagkakataon. Kapag nangyari ito, maaaring ganito ang hitsura:
Exception in thread "Thread-1" java.lang.IllegalStateException: 7899 + 1 = 7901
at App.lambda$main$0(App.java:13)
at java.lang.Thread.run(Thread.java:745)
Gaya ng nakikita mo, may nangyaring mali habang newValue
itinalaga ang isang halaga. newValue
ay masyadong malaki. Dahil sa kondisyon ng lahi, nagawa ng isa sa mga thread na baguhin ang variable value
sa pagitan ng dalawang pahayag. May lahi pala sa pagitan ng mga thread. Ngayon isipin kung gaano kahalaga ang hindi gumawa ng mga katulad na pagkakamali sa mga transaksyon sa pananalapi... Makikita rin dito ang mga halimbawa at diagram: Code para gayahin ang kundisyon ng lahi sa Java thread .
pabagu-bago ng isip
Sa pagsasalita tungkol sa pakikipag-ugnayan ng mga thread, angvolatile
keyword ay nagkakahalaga ng pagbanggit. Tingnan natin ang isang simpleng halimbawa:
public class App {
public static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Runnable whileFlagFalse = () -> {
while(!flag) {
}
System.out.println("Flag is now TRUE");
};
new Thread(whileFlagFalse).start();
Thread.sleep(1000);
flag = true;
}
}
Ang pinaka-kawili-wili, ito ay malamang na hindi gagana. Hindi makikita ng bagong thread ang pagbabago sa flag
field. Upang ayusin ito para sa flag
field, kailangan nating gamitin ang volatile
keyword. Paano at bakit? Ginagawa ng processor ang lahat ng mga aksyon. Ngunit ang mga resulta ng mga kalkulasyon ay dapat na naka-imbak sa isang lugar. Para dito, mayroong pangunahing memorya at mayroong cache ng processor. Ang mga cache ng processor ay parang isang maliit na tipak ng memorya na ginagamit upang ma-access ang data nang mas mabilis kaysa sa pag-access sa pangunahing memorya. Ngunit lahat ng bagay ay may downside: ang data sa cache ay maaaring hindi up-to-date (tulad ng sa halimbawa sa itaas, kapag ang halaga ng flag field ay hindi na-update). Kaya angvolatile
Sinasabi ng keyword sa JVM na hindi namin gustong i-cache ang aming variable. Nagbibigay-daan ito sa napapanahon na resulta na makita sa lahat ng mga thread. Ito ay isang napakasimpleng paliwanag. Tulad ng para sa volatile
keyword, lubos kong inirerekumenda na basahin mo ang artikulong ito . Para sa karagdagang impormasyon, ipinapayo ko rin sa iyo na basahin ang Java Memory Model at Java Volatile Keyword . Bukod pa rito, mahalagang tandaan na iyon volatile
ay tungkol sa visibility, at hindi tungkol sa atomicity ng mga pagbabago. Sa pagtingin sa code sa seksyong "Mga kundisyon ng lahi," makakakita tayo ng tooltip sa IntelliJ IDEA: 
Atomicity
Ang mga operasyon ng atom ay mga operasyon na hindi maaaring hatiin. Halimbawa, ang pagpapatakbo ng pagtatalaga ng isang halaga sa isang variable ay dapat na atomic. Sa kasamaang palad, ang increment na operasyon ay hindi atomic, dahil ang increment ay nangangailangan ng hanggang tatlong operasyon ng CPU: kunin ang lumang halaga, magdagdag ng isa dito, pagkatapos ay i-save ang halaga. Bakit mahalaga ang atomicity? Sa pagpapatakbo ng pagtaas, kung mayroong kundisyon ng lahi, maaaring biglang magbago ang nakabahaging mapagkukunan (ibig sabihin, ang nakabahaging halaga) anumang oras. Bukod pa rito, ang mga operasyong kinasasangkutan ng 64-bit na istruktura, halimbawalong
at double
, ay hindi atomic. Higit pang mga detalye ang mababasa dito: Tiyaking atomicity kapag nagbabasa at nagsusulat ng mga 64-bit na halaga . Ang mga problemang nauugnay sa atomicity ay makikita sa halimbawang ito:
public class App {
public static int value = 0;
public static AtomicInteger atomic = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
value++;
atomic.incrementAndGet();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
Thread.sleep(300);
System.out.println(value);
System.out.println(atomic.get());
}
}
Ang espesyal AtomicInteger
na klase ay palaging magbibigay sa amin ng 30,000, ngunit ang value
ay magbabago paminsan-minsan. Mayroong maikling pangkalahatang-ideya ng paksang ito: Panimula sa Atomic Variables sa Java . Ang algorithm na "compare-and-swap" ay nasa gitna ng mga atomic class. Maaari kang magbasa ng higit pa tungkol dito sa Paghahambing ng mga algorithm na walang lock - CAS at FAA sa halimbawa ng JDK 7 at 8 o sa artikulong Maghambing-at-magpalit sa Wikipedia. 
http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html
Nangyayari-bago
Mayroong isang kawili-wili at mahiwagang konsepto na tinatawag na "nangyayari noon". Bilang bahagi ng iyong pag-aaral ng mga thread, dapat mong basahin ang tungkol dito. Ipinapakita ng ugnayang nangyari-bago ang pagkakasunud-sunod kung saan makikita ang mga pagkilos sa pagitan ng mga thread. Maraming interpretasyon at komentaryo. Narito ang isa sa mga pinakabagong presentasyon sa paksang ito: Java "Happens-Before" Relationships .Buod
Sa pagsusuring ito, na-explore namin ang ilan sa mga detalye kung paano nakikipag-ugnayan ang mga thread. Tinalakay namin ang mga problemang maaaring lumitaw, pati na rin ang mga paraan upang matukoy at maalis ang mga ito. Listahan ng mga karagdagang materyales sa paksa:- I-double check ang pag-lock
- FAQ ng JSR 133 (Java Memory Model).
- IQ 35: Paano maiwasan ang deadlock?
- Concurrency Concepts in Java ni Douglas Hawkins (2017)
GO TO FULL VERSION