థ్రెడ్లు ఎలా సంకర్షణ చెందుతాయి అనే వివరాల యొక్క సంక్షిప్త అవలోకనం. గతంలో, థ్రెడ్లు ఒకదానితో ఒకటి ఎలా సమకాలీకరించబడతాయో మేము చూశాము. ఈసారి మేము థ్రెడ్లు పరస్పర చర్య చేస్తున్నప్పుడు తలెత్తే సమస్యలను పరిశీలిస్తాము మరియు వాటిని ఎలా నివారించాలి అనే దాని గురించి మాట్లాడుతాము. మేము మరింత లోతైన అధ్యయనం కోసం కొన్ని ఉపయోగకరమైన లింక్లను కూడా అందిస్తాము.
మీరు ఇక్కడ ఒక సూపర్ ఉదాహరణను చూడవచ్చు: జావా - థ్రెడ్ ఆకలి మరియు ఫెయిర్నెస్ . ఈ ఉదాహరణ ఆకలితో ఉన్నప్పుడు థ్రెడ్లతో ఏమి జరుగుతుందో మరియు ఒక చిన్న మార్పు మిమ్మల్ని లోడ్ను సమానంగా పంపిణీ చేయడానికి ఎలా అనుమతిస్తుంది
పరిచయం
కాబట్టి, జావాలో థ్రెడ్లు ఉన్నాయని మనకు తెలుసు. మీరు బెటర్ టుగెదర్: జావా మరియు థ్రెడ్ క్లాస్ అనే రివ్యూలో దాని గురించి చదువుకోవచ్చు . పార్ట్ I - అమలు యొక్క థ్రెడ్లు . బెటర్ టుగెదర్: జావా మరియు థ్రెడ్ క్లాస్ అనే రివ్యూలో థ్రెడ్లు ఒకదానితో ఒకటి సింక్రొనైజ్ చేయగలవు అనే వాస్తవాన్ని మేము అన్వేషించాము . పార్ట్ II - సమకాలీకరణ . థ్రెడ్లు ఒకదానితో ఒకటి ఎలా సంకర్షణ చెందుతాయి అనే దాని గురించి మాట్లాడే సమయం ఇది. వారు భాగస్వామ్య వనరులను ఎలా పంచుకుంటారు? ఇక్కడ ఏ సమస్యలు తలెత్తవచ్చు?ప్రతిష్టంభన
అన్నింటికంటే భయంకరమైన సమస్య ప్రతిష్టంభన. డెడ్లాక్ అంటే రెండు లేదా అంతకంటే ఎక్కువ థ్రెడ్లు మరొకదాని కోసం శాశ్వతంగా వేచి ఉండటం. మేము ప్రతిష్టంభనను వివరించే Oracle వెబ్పేజీ నుండి ఒక ఉదాహరణ తీసుకుంటాము :
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();
}
}
డెడ్లాక్ ఇక్కడ మొదటిసారి జరగకపోవచ్చు, కానీ మీ ప్రోగ్రామ్ హ్యాంగ్ అయితే, రన్ చేయడానికి ఇది సమయం jvisualvm
: 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
థ్రెడ్ 1 థ్రెడ్ 0 నుండి లాక్ కోసం వేచి ఉంది. అది ఎందుకు జరుగుతుంది? Thread-1
అమలు చేయడం ప్రారంభిస్తుంది మరియు Friend#bow
పద్ధతిని అమలు చేస్తుంది. ఇది కీవర్డ్తో గుర్తించబడింది synchronized
, అంటే మనం this
(ప్రస్తుత వస్తువు) కోసం మానిటర్ను పొందుతున్నాము. పద్ధతి యొక్క ఇన్పుట్ ఇతర వస్తువుకు సూచన Friend
. ఇప్పుడు, Thread-1
మరొక పద్ధతిని అమలు చేయాలనుకుంటున్నారు Friend
మరియు అలా చేయడానికి దాని లాక్ని తప్పనిసరిగా పొందాలి. కానీ ఇతర థ్రెడ్ (ఈ సందర్భంలో Thread-0
) పద్ధతిని నమోదు చేయగలిగితే bow()
, లాక్ ఇప్పటికే పొందబడింది మరియు Thread-1
వేచి ఉందిThread-0
, మరియు వైస్ వెర్సా. ఇది ప్రతిష్టంభన అనేది పరిష్కరించలేనిది మరియు మేము దీనిని ప్రతిష్టంభన అంటాము. విడుదల చేయలేని చావు పట్టు వలె, ప్రతిష్టంభన అనేది విచ్ఛిన్నం చేయలేని పరస్పర అడ్డంకి. డెడ్లాక్ గురించి మరొక వివరణ కోసం, మీరు ఈ వీడియోను చూడవచ్చు: డెడ్లాక్ మరియు లైవ్లాక్ వివరించబడింది .
లైవ్లాక్
ప్రతిష్టంభన ఉంటే, లైవ్లాక్ కూడా ఉందా? అవును, ఉంది :) థ్రెడ్లు బాహ్యంగా సజీవంగా ఉన్నట్లు అనిపించినప్పుడు లైవ్లాక్ జరుగుతుంది, కానీ అవి ఏమీ చేయలేక పోతున్నాయి, ఎందుకంటే వారి పనిని కొనసాగించడానికి అవసరమైన షరతులు (లు) నెరవేర్చబడవు. ప్రాథమికంగా, లైవ్లాక్ డెడ్లాక్ మాదిరిగానే ఉంటుంది, కానీ థ్రెడ్లు మానిటర్ కోసం వేచి ఉండవు. బదులుగా, వారు ఎప్పటికీ ఏదో చేస్తూ ఉంటారు. ఉదాహరణకి:
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();
}
}
జావా థ్రెడ్ షెడ్యూలర్ థ్రెడ్లను ప్రారంభించే క్రమంలో ఈ కోడ్ విజయం ఆధారపడి ఉంటుంది. మొదట ప్రారంభమైతే Thead-1
, మనకు లైవ్లాక్ లభిస్తుంది:
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
...
మీరు ఉదాహరణ నుండి చూడగలిగినట్లుగా, రెండు థ్రెడ్లు రెండు తాళాలను పొందేందుకు ప్రయత్నిస్తాయి, కానీ అవి విఫలమవుతాయి. కానీ, అవి ప్రతిష్టంభనలో లేవు. బాహ్యంగా, ప్రతిదీ బాగానే ఉంది మరియు వారు తమ పనిని చేస్తున్నారు. JVisualVM ప్రకారం, మేము నిద్ర కాలాలు మరియు ఉద్యానవనం యొక్క కాలాన్ని చూస్తాము (ఇది ఒక థ్రెడ్ లాక్ని పొందేందుకు ప్రయత్నించినప్పుడు - ఇది పార్క్ స్థితిలోకి ప్రవేశిస్తుంది, మేము థ్రెడ్ సింక్రొనైజేషన్ గురించి మాట్లాడినప్పుడు ఇంతకు ముందు చర్చించాము) . మీరు ఇక్కడ లైవ్లాక్ యొక్క ఉదాహరణను చూడవచ్చు: జావా - థ్రెడ్ లైవ్లాక్ .
ఆకలిచావు
డెడ్లాక్ మరియు లైవ్లాక్తో పాటు, మల్టీథ్రెడింగ్ సమయంలో సంభవించే మరొక సమస్య ఉంది: ఆకలి. ఈ దృగ్విషయం థ్రెడ్లు నిరోధించబడనందున మునుపటి బ్లాకింగ్ రూపాల నుండి భిన్నంగా ఉంటుంది - వాటికి తగినంత వనరులు లేవు. ఫలితంగా, కొన్ని థ్రెడ్లు మొత్తం ఎగ్జిక్యూషన్ సమయాన్ని తీసుకుంటే, మరికొన్ని రన్ చేయలేకపోతున్నాయి:https://www.logicbig.com/
Thread.sleep()
.Thread.wait()
జాతి పరిస్థితులు
మల్టీథ్రెడింగ్లో, "జాతి పరిస్థితి" వంటి విషయం ఉంది. థ్రెడ్లు వనరును పంచుకున్నప్పుడు ఈ దృగ్విషయం జరుగుతుంది, అయితే కోడ్ సరైన భాగస్వామ్యాన్ని నిర్ధారించని విధంగా వ్రాయబడుతుంది. ఒక ఉదాహరణను పరిశీలించండి:
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();
}
}
ఈ కోడ్ మొదటిసారి లోపాన్ని సృష్టించకపోవచ్చు. అది చేసినప్పుడు, ఇది ఇలా ఉండవచ్చు:
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)
మీరు గమనిస్తే, newValue
విలువను కేటాయించేటప్పుడు ఏదో తప్పు జరిగింది. newValue
చాలా పెద్దది. value
రేస్ పరిస్థితి కారణంగా, థ్రెడ్లలో ఒకటి రెండు స్టేట్మెంట్ల మధ్య వేరియబుల్లను మార్చగలిగింది . థ్రెడ్ల మధ్య రేసు ఉందని తేలింది. ద్రవ్య లావాదేవీలతో ఇలాంటి పొరపాట్లు చేయకుండా ఉండటం ఎంత ముఖ్యమో ఇప్పుడు ఆలోచించండి... ఉదాహరణలు మరియు రేఖాచిత్రాలు కూడా ఇక్కడ చూడవచ్చు: జావా థ్రెడ్లో జాతి పరిస్థితిని అనుకరించడానికి కోడ్ .
త్వరగా ఆవిరి అయ్యెడు
థ్రెడ్ల పరస్పర చర్య గురించి మాట్లాడుతూ,volatile
కీవర్డ్ ప్రస్తావించదగినది. ఒక సాధారణ ఉదాహరణ చూద్దాం:
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;
}
}
చాలా ఆసక్తికరమైన విషయం ఏమిటంటే, ఇది పని చేయని అవకాశం ఉంది. కొత్త థ్రెడ్ ఫీల్డ్లో మార్పును చూడదు flag
. ఫీల్డ్ కోసం దీన్ని పరిష్కరించడానికి flag
, మేము కీవర్డ్ని ఉపయోగించాలి volatile
. ఎలా మరియు ఎందుకు? ప్రాసెసర్ అన్ని చర్యలను నిర్వహిస్తుంది. కానీ లెక్కల ఫలితాలు ఎక్కడా నిల్వ చేయబడాలి. దీని కోసం, ప్రధాన మెమరీ ఉంది మరియు ప్రాసెసర్ యొక్క కాష్ ఉంది. ప్రాసెసర్ యొక్క కాష్లు మెయిన్ మెమొరీని యాక్సెస్ చేస్తున్నప్పుడు కంటే వేగంగా డేటాను యాక్సెస్ చేయడానికి ఉపయోగించే చిన్న మెమరీ భాగం లాంటివి. కానీ ప్రతిదానికీ ప్రతికూలత ఉంది: కాష్లోని డేటా తాజాగా ఉండకపోవచ్చు (పై ఉదాహరణలో, ఫ్లాగ్ ఫీల్డ్ విలువ నవీకరించబడనప్పుడు). కాబట్టి, దిvolatile
కీవర్డ్ JVM కి మన వేరియబుల్ని కాష్ చేయకూడదని చెబుతుంది. ఇది అన్ని థ్రెడ్లలో తాజా ఫలితాన్ని చూడటానికి అనుమతిస్తుంది. ఇది చాలా సరళమైన వివరణ. కీవర్డ్ విషయానికొస్తే , మీరు ఈ కథనాన్నిvolatile
చదవాలని నేను బాగా సిఫార్సు చేస్తున్నాను . మరింత సమాచారం కోసం, జావా మెమరీ మోడల్ మరియు జావా వోలటైల్ కీవర్డ్ చదవమని కూడా నేను మీకు సలహా ఇస్తున్నాను . అదనంగా, ఇది దృశ్యమానత గురించి గుర్తుంచుకోవడం ముఖ్యం , మరియు మార్పుల పరమాణుత్వం గురించి కాదు. "జాతి పరిస్థితులు" విభాగంలోని కోడ్ను పరిశీలిస్తే, మేము IntelliJ IDEAలో టూల్టిప్ను చూస్తాము: ఈ తనిఖీ IDEA-61117 సంచికలో భాగంగా IntelliJ IDEAకి జోడించబడింది , ఇది 2010లో విడుదల నోట్స్లో జాబితా చేయబడింది .volatile
పరమాణువు
అణు కార్యకలాపాలు విభజించబడని కార్యకలాపాలు. ఉదాహరణకు, వేరియబుల్కు విలువను కేటాయించే ఆపరేషన్ తప్పనిసరిగా పరమాణువుగా ఉండాలి. దురదృష్టవశాత్తూ, ఇంక్రిమెంట్ ఆపరేషన్ అటామిక్ కాదు, ఎందుకంటే ఇంక్రిమెంట్కి మూడు CPU ఆపరేషన్లు అవసరం: పాత విలువను పొందండి, దానికి ఒకదాన్ని జోడించి, ఆపై విలువను సేవ్ చేయండి. పరమాణువు ఎందుకు ముఖ్యమైనది? ఇంక్రిమెంట్ ఆపరేషన్తో, రేస్ కండిషన్ ఉంటే, భాగస్వామ్య వనరు (అంటే భాగస్వామ్య విలువ) ఏ సమయంలోనైనా అకస్మాత్తుగా మారవచ్చు. అదనంగా, 64-బిట్ నిర్మాణాలతో కూడిన కార్యకలాపాలు, ఉదాహరణకుlong
మరియు double
, పరమాణువు కాదు. మరిన్ని వివరాలను ఇక్కడ చదవవచ్చు: 64-బిట్ విలువలను చదివేటప్పుడు మరియు వ్రాసేటప్పుడు పరమాణుత్వాన్ని నిర్ధారించుకోండి . పరమాణుత్వానికి సంబంధించిన సమస్యలను ఈ ఉదాహరణలో చూడవచ్చు:
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());
}
}
ప్రత్యేక AtomicInteger
తరగతి మాకు ఎల్లప్పుడూ 30,000 ఇస్తుంది, కానీ value
ఎప్పటికప్పుడు మారుతుంది. ఈ అంశం యొక్క చిన్న అవలోకనం ఉంది: జావాలో అటామిక్ వేరియబుల్స్ పరిచయం . "పోలిక-మరియు-స్వాప్" అల్గోరిథం పరమాణు తరగతుల గుండె వద్ద ఉంది. మీరు దాని గురించి మరింత ఇక్కడ JDK 7 మరియు 8 ఉదాహరణలో లాక్-ఫ్రీ అల్గారిథమ్ల పోలికలో - CAS మరియు FAA లో లేదా వికీపీడియాలోని కంపేర్-అండ్-స్వాప్ కథనంలో చదవవచ్చు .
http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html
జరుగుతుంది-ముందు
"ముందు జరుగుతుంది" అనే ఆసక్తికరమైన మరియు రహస్యమైన భావన ఉంది. మీ థ్రెడ్ల అధ్యయనంలో భాగంగా, మీరు దాని గురించి చదవాలి. థ్రెడ్ల మధ్య చర్యలు చూడబడే క్రమాన్ని జరిగే-ముందు సంబంధం చూపిస్తుంది. అనేక వివరణలు మరియు వ్యాఖ్యానాలు ఉన్నాయి. ఈ విషయంపై ఇటీవలి ప్రెజెంటేషన్లలో ఒకటి ఇక్కడ ఉంది: జావా "జరుగుతుంది-ముందు" సంబంధాలు .సారాంశం
ఈ సమీక్షలో, థ్రెడ్లు ఎలా ఇంటరాక్ట్ అవుతాయి అనేదానికి సంబంధించిన కొన్ని ప్రత్యేకతలను మేము అన్వేషించాము. మేము తలెత్తే సమస్యలను అలాగే వాటిని గుర్తించి తొలగించే మార్గాలను చర్చించాము. అంశంపై అదనపు పదార్థాల జాబితా:- రెండుసార్లు తనిఖీ చేసిన లాకింగ్
- JSR 133 (జావా మెమరీ మోడల్) తరచుగా అడిగే ప్రశ్నలు
- IQ 35: ప్రతిష్టంభనను ఎలా నివారించాలి?
- డగ్లస్ హాకిన్స్ (2017) ద్వారా జావాలో కాన్కరెన్సీ కాన్సెప్ట్లు
GO TO FULL VERSION