CodeGym/Java Blog/अनियमित/बेहतर एक साथ: जावा और थ्रेड क्लास। भाग III - इंटरेक्शन
John Squirrels
स्तर 41
San Francisco

बेहतर एक साथ: जावा और थ्रेड क्लास। भाग III - इंटरेक्शन

अनियमित ग्रुप में प्रकाशित
सदस्य
थ्रेड्स कैसे इंटरैक्ट करते हैं, इसके विवरण का एक संक्षिप्त अवलोकन। पहले, हमने देखा कि थ्रेड्स एक दूसरे के साथ कैसे सिंक्रोनाइज़ होते हैं। इस बार हम उन समस्याओं पर विचार करेंगे जो थ्रेड्स के इंटरैक्ट करने पर उत्पन्न हो सकती हैं, और हम बात करेंगे कि उनसे कैसे बचा जाए। अधिक गहन अध्ययन के लिए हम कुछ उपयोगी लिंक भी प्रदान करेंगे। बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - सहभागिता - 1

परिचय

तो, हम जानते हैं कि जावा में थ्रेड्स हैं। आप इसके बारे में बेहतर एक साथ समीक्षा में पढ़ सकते हैं : जावा और थ्रेड क्लास। भाग I - निष्पादन के सूत्रऔर हमने इस तथ्य की खोज की कि धागे एक दूसरे के साथ बेटर टुगेदर: जावा और थ्रेड क्लास नामक समीक्षा में सिंक्रनाइज़ कर सकते हैं। भाग II - तुल्यकालन । यह बात करने का समय है कि थ्रेड्स एक दूसरे के साथ कैसे इंटरैक्ट करते हैं। वे साझा संसाधनों को कैसे साझा करते हैं? यहाँ क्या समस्याएँ उत्पन्न हो सकती हैं? बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - इंटरेक्शन - 2

गतिरोध

सभी की सबसे भयानक समस्या डेडलॉक है। गतिरोध तब होता है जब दो या दो से अधिक धागे हमेशा के लिए दूसरे की प्रतीक्षा कर रहे होते हैं। हम 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: बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - इंटरेक्शन - 3JVisualVM प्लगइन स्थापित (टूल्स -> प्लगइन्स के माध्यम से) के साथ, हम देख सकते हैं कि डेडलॉक कहाँ हुआ:
"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, और इसके विपरीत। यह गतिरोध अघुलनशील है, और हम इसे गतिरोध कहते हैं। मौत की पकड़ की तरह जिसे छोड़ा नहीं जा सकता, गतिरोध पारस्परिक अवरोधन है जिसे तोड़ा नहीं जा सकता। डेडलॉक की अन्य व्याख्या के लिए आप यह वीडियो देख सकते हैं: डेडलॉक और लाइवलॉक एक्सप्लेन

लाइवलॉक

अगर डेडलॉक है तो क्या लाइवलॉक भी है? हाँ, वहाँ है :) Livelock तब होता है जब धागे बाहरी रूप से जीवित प्रतीत होते हैं, लेकिन वे कुछ भी करने में असमर्थ होते हैं, क्योंकि उनके काम को जारी रखने के लिए आवश्यक शर्तें पूरी नहीं की जा सकती हैं। मूल रूप से, लाइवलॉक गतिरोध के समान है, लेकिन थ्रेड्स मॉनिटर के इंतजार में "हैंग" नहीं होते हैं। इसके बजाय, वे हमेशा कुछ न कुछ करते रहते हैं। उदाहरण के लिए:
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
...
जैसा कि आप उदाहरण से देख सकते हैं, दोनों धागे बारी-बारी से दोनों तालों को प्राप्त करने का प्रयास करते हैं, लेकिन वे विफल हो जाते हैं। लेकिन, वे गतिरोध में नहीं हैं। बाह्य रूप से, सब कुछ ठीक है और वे अपना काम कर रहे हैं। बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - इंटरेक्शन - 4JVisualVM के अनुसार, हम नींद की अवधि और पार्क की अवधि देखते हैं (यह तब होता है जब कोई थ्रेड लॉक प्राप्त करने का प्रयास करता है - यह पार्क स्थिति में प्रवेश करता है, जैसा कि हमने पहले चर्चा की थी जब हमने थ्रेड सिंक्रोनाइज़ेशन के बारे में बात की थी ) । आप यहां लाइवलॉक का एक उदाहरण देख सकते हैं: जावा - थ्रेड लाइवलॉक

भुखमरी

डेडलॉक और लाइवलॉक के अलावा, एक और समस्या है जो मल्टीथ्रेडिंग के दौरान हो सकती है: भुखमरी। यह घटना अवरुद्ध करने के पिछले रूपों से भिन्न है जिसमें धागे अवरुद्ध नहीं हैं - उनके पास पर्याप्त संसाधन नहीं हैं। नतीजतन, जबकि कुछ धागे सभी निष्पादन समय लेते हैं, अन्य चलाने में असमर्थ हैं: बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - सहभागिता - 5

https://www.logicbig.com/

आप यहां एक सुपर उदाहरण देख सकते हैं: जावा - थ्रेड भुखमरी और निष्पक्षता । यह उदाहरण दिखाता है कि भुखमरी के दौरान धागे के साथ क्या होता है और कैसे एक छोटे से परिवर्तन से Thread.sleep()आप Thread.wait()भार को समान रूप से वितरित कर सकते हैं। बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - इंटरेक्शन - 6

दौड़ की स्थिति

मल्टीथ्रेडिंग में, "रेस कंडीशन" जैसी कोई चीज होती है। यह घटना तब होती है जब धागे संसाधन साझा करते हैं, लेकिन कोड इस तरह लिखा जाता है कि यह सही साझाकरण सुनिश्चित नहीं करता है। एक उदाहरण देखें:
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​​कीवर्ड का सवाल है, मैं अत्यधिक अनुशंसा करता हूं कि आप इस लेख को पढ़ें । अधिक जानकारी के लिए, मैं आपको जावा मेमोरी मॉडल और जावा वाष्पशील कीवर्ड पढ़ने की भी सलाह देता हूं । इसके अतिरिक्त, यह याद रखना महत्वपूर्ण है कि यह volatileदृश्यता के बारे में है, न कि परिवर्तनों की परमाणुता के बारे में। "दौड़ की स्थिति" अनुभाग में कोड को देखते हुए, हम IntelliJ IDEA में एक टूलटिप देखेंगे: बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - सहभागिता - 7यह निरीक्षण IntelliJ IDEA में IDEA-61117 जारी करने के भाग के रूप में जोड़ा गया था , जिसे 2010 में रिलीज़ नोट्स में सूचीबद्ध किया गया था।

परमाणुता

परमाणु संचालन ऐसे ऑपरेशन हैं जिन्हें विभाजित नहीं किया जा सकता है। उदाहरण के लिए, एक चर को मान निर्दिष्ट करने की संक्रिया परमाणु होनी चाहिए। दुर्भाग्य से, इंक्रीमेंट ऑपरेशन परमाणु नहीं है, क्योंकि इंक्रीमेंट के लिए तीन सीपीयू ऑपरेशंस की आवश्यकता होती है: पुराना मान प्राप्त करें, उसमें एक जोड़ें, फिर मान सहेजें। परमाणुता क्यों महत्वपूर्ण है? वृद्धि ऑपरेशन के साथ, यदि दौड़ की स्थिति है, तो साझा संसाधन (यानी साझा मूल्य) किसी भी समय अचानक बदल सकता है। इसके अतिरिक्त, 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समय-समय पर बदल जाएगा। इस विषय का एक संक्षिप्त अवलोकन है: जावा में परमाणु चर का परिचय । "तुलना-और-स्वैप" एल्गोरिथ्म परमाणु वर्गों के केंद्र में स्थित है। आप इसके बारे में लॉक-फ्री एल्गोरिदम की तुलना - CAS और FAA में JDK 7 और 8 के उदाहरण पर या विकिपीडिया पर तुलना-और-स्वैप लेख में इसके बारे में अधिक पढ़ सकते हैं। बेहतर एक साथ: जावा और थ्रेड क्लास।  भाग III - सहभागिता - 9

http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html

होता है-पहले

"पहले होता है" नामक एक दिलचस्प और रहस्यमय अवधारणा है। धागे के अध्ययन के हिस्से के रूप में, आपको इसके बारे में पढ़ना चाहिए। होता है-पहले संबंध उस क्रम को दर्शाता है जिसमें थ्रेड्स के बीच क्रियाएं दिखाई देंगी। कई व्याख्याएं और टिप्पणियां हैं। यहाँ इस विषय पर सबसे हालिया प्रस्तुतियों में से एक है: Java "हैपन्स-बिफोर" रिलेशनशिप

सारांश

इस समीक्षा में, हमने कुछ विशिष्टताओं की खोज की है कि थ्रेड्स कैसे इंटरैक्ट करते हैं। हमने उन समस्याओं पर चर्चा की जो उत्पन्न हो सकती हैं, साथ ही उन्हें पहचानने और समाप्त करने के तरीके भी। विषय पर अतिरिक्त सामग्री की सूची: बेहतर एक साथ: जावा और थ्रेड क्लास। भाग I - थ्रेड्स ऑफ़ एक्जीक्यूशन बेहतर एक साथ: जावा और थ्रेड क्लास। भाग II - तुल्यकालन बेहतर एक साथ: जावा और थ्रेड वर्ग। भाग IV - कॉल करने योग्य, भविष्य और मित्र बेहतर एक साथ: जावा और थ्रेड वर्ग। भाग V - एक्ज़ीक्यूटर, थ्रेडपूल, फोर्क / जॉइन बेटर टुगेदर: जावा और थ्रेड क्लास। भाग VI - आग बुझाओ!
टिप्पणियां
  • लोकप्रिय
  • नया
  • पुराना
टिप्पणी लिखने के लिए आपको साइन इन करना होगा
इस पेज पर अभी तक कोई टिप्पणियां नहीं हैं