जावा सिंक्रनाइज़ किए गए विधि लॉक ऑब्जेक्ट पर, या विधि?


191

यदि मेरे पास एक ही कक्षा में 2 सिंक्रनाइज़ किए गए तरीके हैं, लेकिन प्रत्येक अलग-अलग चर को एक्सेस कर रहा है, तो क्या 2 धागे एक ही समय में उन 2 विधियों तक पहुंच सकते हैं? क्या लॉक ऑब्जेक्ट पर होता है, या क्या यह सिंक्रनाइज़ेशन विधि के अंदर चर के रूप में विशिष्ट है?

उदाहरण:

class X {

    private int a;
    private int b;

    public synchronized void addA(){
        a++;
    }

    public synchronized void addB(){
        b++;
    }

}

क्या 2 धागे कक्षा X के प्रदर्शन का एक ही उदाहरण x.addA() और x.addB()एक ही समय में उपयोग कर सकते हैं?

जवाबों:


197

यदि आप विधि को सिंक्रनाइज़ के रूप में घोषित करते हैं (जैसा कि आप टाइप करके कर रहे हैं public synchronized void addA()) तो आप पूरे ऑब्जेक्ट पर सिंक्रोनाइज़ करते हैं , इसलिए इस समान ऑब्जेक्ट से एक अलग वेरिएबल तक पहुंचने वाले दो धागे वैसे भी एक दूसरे को ब्लॉक कर देंगे।

यदि आप एक समय में केवल एक चर पर सिंक्रनाइज़ करना चाहते हैं, तो दो चर अलग-अलग चर का उपयोग करते हुए एक दूसरे को ब्लॉक नहीं करेंगे, आपने उन पर अलग-अलग synchronized ()ब्लॉकों में सिंक्रनाइज़ किया है । तो aऔर bआप का प्रयोग करेंगे वस्तु संदर्भ थे:

public void addA() {
    synchronized( a ) {
        a++;
    }
}

public void addB() {
    synchronized( b ) {
        b++;
    }
}

लेकिन जब से वे आदिम हैं आप ऐसा नहीं कर सकते।

मैं आपको इसके बजाय AtomicInteger का उपयोग करने का सुझाव दूंगा :

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}

181
यदि आप उस विधि पर सिंक्रोनाइज़ करते हैं जिसे आप पूरे ऑब्जेक्ट को लॉक करते हैं, तो इसी ऑब्जेक्ट से एक अलग वेरिएबल तक पहुंचने वाले दो धागे वैसे भी एक-दूसरे को ब्लॉक कर देंगे। यह थोड़ा भ्रामक है। विधि synchronized (this)के शरीर के चारों ओर एक ब्लॉक होने पर विधि पर सिंक्रनाइज़ करना कार्यात्मक रूप से समतुल्य है । ऑब्जेक्ट "यह" लॉक नहीं हो जाता है, बल्कि ऑब्जेक्ट "इस" को म्यूटेक्स के रूप में उपयोग किया जाता है और शरीर को अन्य कोड खंडों के साथ समवर्ती रूप से निष्पादित करने से रोका जाता है। यह "यह" के अन्य क्षेत्रों / विधियों पर कोई प्रभाव नहीं है जो सिंक्रनाइज़ नहीं हैं।
मार्क पीटर्स

13
हां, यह वास्तव में भ्रामक है। वास्तविक उदाहरण के लिए - इसे देखें - stackoverflow.com/questions/14447095/… - सारांश: लॉकिंग केवल सिंक्रोनाइज़्ड मेथड लेवल पर है और ऑब्जेक्ट के इंस्टेंस वेरिएबल को अन्य थ्रेड द्वारा एक्सेस किया जा सकता है
mac

5
पहला उदाहरण मौलिक रूप से टूटा हुआ है। यदि aऔर bऑब्जेक्ट्स थे, उदाहरण के लिए Integer, आप ऑपरेटर को लागू करते समय विभिन्न वस्तुओं के साथ बदल रहे उदाहरणों पर सिंक्रनाइज़ कर रहे थे ++
होल्गर

अपने उत्तर को ठीक करें और एटोमिकइंटरइगर को इनिशियलाइज़ करें: एटॉमिकइंटरएजर ए = न्यू एटोमिकइंटर (0);
मेहदी

हो सकता है कि इस anwser को इस वस्तु में खुद को सिंक्रोनाइज़ करने के बारे में अन्य
बातों के

71

विधि घोषणा पर सिंक्रोनाइज़ किया गया है, इसके लिए वाक्य रचना चीनी है:

 public void addA() {
     synchronized (this) {
          a++;
     }
  }

एक स्थिर विधि पर यह इसके लिए चीनी की चीनी है:

 ClassA {
     public static void addA() {
          synchronized(ClassA.class) {
              a++;
          }
 }

मुझे लगता है कि अगर जावा डिज़ाइनर्स को पता था कि अब सिंक्रोनाइज़ेशन के बारे में क्या समझा गया है, तो उन्होंने सिंटैक्टिकल शुगर को नहीं जोड़ा होगा, क्योंकि यह अधिक बार कंसिस्टेंसी के खराब कार्यान्वयन की ओर नहीं जाता है।


3
सच नहीं। सिंक्रनाइज़ विधि सिंक्रनाइज़ (ऑब्जेक्ट) की तुलना में अलग-अलग बायटेकोड उत्पन्न करती है। जबकि कार्यक्षमता समतुल्य है, यह सिंटैक्टिक शुगर से अधिक है।
स्टीव कू

10
मुझे नहीं लगता कि "वाक्य रचना चीनी" को बाइट-कोड समकक्ष के रूप में कड़ाई से परिभाषित किया गया है। मुद्दा यह है कि यह कार्यात्मक रूप से समतुल्य है।
यिशै

1
यदि जावा डिजाइनरों को पता था कि पहले से ही मॉनिटर के बारे में क्या जाना जाता है, तो वे मूल रूप से यूनिक्स की पारी का अनुकरण करने के बजाय इसे अलग तरह से करना चाहिए था। पेर ब्रिंच हांसेन कहा 'स्पष्ट रूप से मैं व्यर्थ में मेहनत की है' जब वह जावा संगामिति पुरातन देखा
लोर्न

यह सच है। ओपी द्वारा दिया गया उदाहरण प्रत्येक विधि को लॉक करने के लिए दिखाई देगा लेकिन वास्तव में वे सभी एक ही वस्तु पर लॉक होते हैं। बहुत भ्रामक वाक्यविन्यास। 10+ वर्षों के लिए जावा का उपयोग करने के बाद मुझे यह नहीं पता था। इसलिए मैं इस कारण से सिंक्रनाइज़ किए गए तरीकों से बचूंगा। मैंने हमेशा सोचा था कि प्रत्येक विधि के लिए एक अदृश्य वस्तु बनाई गई थी जिसे सिंक्रनाइज़ के साथ परिभाषित किया गया था।
पीटर क्विंग

21

सिंक्रनाइज़ तरीकों पर "द जावा ™ ट्यूटोरियल" से :

सबसे पहले, एक ही वस्तु पर इंटरलेव करने के लिए सिंक्रनाइज़ किए गए तरीकों के दो आह्वान संभव नहीं है । जब कोई थ्रेड किसी ऑब्जेक्ट के लिए एक सिंक्रोनाइज़ की गई विधि को निष्पादित कर रहा होता है, तो अन्य सभी थ्रेड्स जो ऑब्जेक्ट के साथ पहले थ्रेड को निष्पादित करने तक एक ही ऑब्जेक्ट ब्लॉक (सस्पेंड निष्पादन) के लिए सिंक्रनाइज़ किए गए तरीकों को लागू करते हैं।

सिंक्रनाइज़ किए गए ब्लॉक पर "द जावा ™ ट्यूटोरियल" से :

सिंक्रोनाइज्ड स्टेटमेंट्स सुस्पष्ट दानेदार तुल्यकालन के साथ संगति में सुधार के लिए भी उपयोगी हैं। मान लीजिए, उदाहरण के लिए, वर्ग MsLunch में दो उदाहरण फ़ील्ड, c1 और c2 हैं, जिनका उपयोग कभी एक साथ नहीं किया जाता है। इन फ़ील्ड्स के सभी अद्यतनों को सिंक्रनाइज़ किया जाना चाहिए, लेकिन c1 के अपडेट को c2 के अपडेट के साथ इंटरलीव्ड होने से रोकने का कोई कारण नहीं है - और ऐसा करने से अनावश्यक अवरोधन बनाकर संग़ठन कम हो जाती है। सिंक्रनाइज़ विधियों का उपयोग करने या अन्यथा इससे जुड़े लॉक का उपयोग करने के बजाय, हम केवल ताले प्रदान करने के लिए दो ऑब्जेक्ट बनाते हैं।

(जोर मेरा)

मान लीजिए कि आपके पास 2 गैर-इंटरलेविंग चर हैं। इसलिए आप एक ही समय में अलग-अलग थ्रेड से हर एक तक पहुंचना चाहते हैं। आपको ऑब्जेक्ट क्लास पर ही नहीं, बल्कि नीचे दी गई क्लास ऑब्जेक्ट (उदाहरण के लिए दूसरा ओरेकल लिंक) से लॉक को परिभाषित करने की आवश्यकता है :

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

14

लॉक एक्सेस ऑब्जेक्ट पर है, विधि पर नहीं। विधि के भीतर कौन से चर एक्सेस किए जाते हैं यह अप्रासंगिक है।

विधि में "सिंक्रनाइज़" जोड़ने का मतलब है कि कोड को चलाने वाले धागे को आगे बढ़ने से पहले ऑब्जेक्ट पर ताला प्राप्त करना होगा। "स्टैटिक सिंक्रोनाइज़" को जोड़ने का मतलब है कि कोड को चलाने वाले धागे को आगे बढ़ने से पहले क्लास ऑब्जेक्ट पर लॉक हासिल करना होगा। वैकल्पिक रूप से आप इस तरह से एक ब्लॉक में कोड लपेट सकते हैं:

public void addA() {
    synchronized(this) {
        a++;
    }
}

ताकि आप उस वस्तु को निर्दिष्ट कर सकें जिसका ताला अधिग्रहित किया जाना चाहिए।

यदि आप उस ऑब्जेक्ट पर ताला लगाने से बचना चाहते हैं जिसे आप चुन सकते हैं:


7

ओरेकल प्रलेखन लिंक से

सिंक्रनाइज़ किए जा रहे तरीकों के दो प्रभाव हैं:

सबसे पहले, यह एक ही वस्तु पर interleave करने के लिए सिंक्रनाइज़ तरीकों के दो आह्वान के लिए संभव नहीं है। जब कोई थ्रेड किसी ऑब्जेक्ट के लिए एक सिंक्रोनाइज़ की गई विधि को निष्पादित कर रहा होता है, तो अन्य सभी थ्रेड्स जो ऑब्जेक्ट के साथ पहले थ्रेड को निष्पादित करने तक एक ही ऑब्जेक्ट ब्लॉक (सस्पेंड निष्पादन) के लिए सिंक्रनाइज़ किए गए तरीकों को लागू करते हैं।

दूसरा, जब एक सिंक्रनाइज़ की गई विधि बाहर निकलती है, तो यह स्वचालित रूप से एक ही ऑब्जेक्ट के लिए एक सिंक्रनाइज़ किए गए विधि के किसी भी बाद के आह्वान के साथ एक-पहले संबंध स्थापित करता है। यह गारंटी देता है कि ऑब्जेक्ट की स्थिति में परिवर्तन सभी थ्रेड्स को दिखाई देते हैं

आंतरिक ताले और लॉक व्यवहार को समझने के लिए इस दस्तावेज़ पृष्ठ पर एक नज़र डालें ।

यह आपके प्रश्न का उत्तर देगा: समान ऑब्जेक्ट x पर, आप x.addA () और x.addB () को एक ही समय में कॉल नहीं कर सकते हैं जब एक सिंक्रनाइज़ किए गए तरीकों का निष्पादन चल रहा हो।


4

यदि आपके पास कुछ विधियां हैं जो सिंक्रनाइज़ नहीं हैं और इंस्टेंस चर को एक्सेस और बदल रहे हैं। आपके उदाहरण में:

 private int a;
 private int b;

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

 public void changeState() {
      a++;
      b++;
    }

आपको इस परिदृश्य से बचने की आवश्यकता है कि गैर सिंक्रनाइज़ तरीके इंस्टेंस चर तक पहुंच रहे हैं और इसे बदल रहे हैं अन्यथा सिंक्रनाइज़ किए गए तरीकों का उपयोग करने का कोई मतलब नहीं है।

नीचे के परिदृश्य में: -

class X {

        private int a;
        private int b;

        public synchronized void addA(){
            a++;
        }

        public synchronized void addB(){
            b++;
        }
     public void changeState() {
          a++;
          b++;
        }
    }

केवल थ्रेड्स में से कोई भी addA या addB मेथड में हो सकता है लेकिन एक ही समय में थ्रेड्स की कोई भी संख्या changeState मेथड में प्रवेश कर सकती है। कोई भी दो थ्रेड addA और addB को एक ही समय (ऑब्जेक्ट स्तर लॉकिंग के कारण) दर्ज नहीं कर सकते हैं, लेकिन एक ही समय में थ्रेड्स की कोई भी संख्या changeState में प्रवेश कर सकती है।


3

आप निम्न की तरह कुछ कर सकते हैं। इस स्थिति में आप "इस" पर लॉक के बजाय सिंक्रनाइज़ करने के लिए a और b पर लॉक का उपयोग कर रहे हैं। हम int का उपयोग नहीं कर सकते क्योंकि आदिम मूल्यों में ताले नहीं होते हैं, इसलिए हम पूर्णांक का उपयोग करते हैं।

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}

3

हां, यह दूसरी विधि को अवरुद्ध कर देगा क्योंकि सिंक्रनाइज़ की गई विधि WHOLE वर्ग ऑब्जेक्ट पर इंगित की गई के रूप में लागू होती है .... लेकिन वैसे भी यह अन्य थ्रेड निष्पादन को रोक देगा केवल उसी तरीके से योग का प्रदर्शन करते हुए जो भी addA या addB में प्रवेश करता है, क्योंकि जब यह खत्म होता है ... एक थ्रेड ऑब्जेक्ट को फ्री करेगा और दूसरा थ्रेड दूसरे तरीके को एक्सेस करेगा और इसी तरह पूरी तरह से काम करेगा।

मेरा मतलब है कि "सिंक्रनाइज़" एक विशिष्ट कोड निष्पादन में दूसरे धागे को एक्सेस करने से रोकने के लिए सटीक रूप से बनाया गया है। तो इस कोड को पूरी तरह से काम करेगा।

अंतिम नोट के रूप में, यदि कोई 'a' और 'b' वैरिएबल है, न कि केवल एक अनोखा वैरिएबल 'a' या जो भी अन्य नाम है, इस तरीके को सिंक्रोनाइज़ करने की कोई आवश्यकता नहीं है क्योंकि यह अन्य var (अन्य मेमोरी) को पूरी तरह से सुरक्षित करने का कारण है स्थान)।

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

साथ ही काम करेंगे


2

यह उदाहरण (हालांकि सुंदर नहीं है) लॉकिंग तंत्र में अधिक जानकारी प्रदान कर सकता है। यदि incrementA है सिंक्रनाइज़ , और incrementB है सिंक्रनाइज़ नहीं है, तो incrementB यथाशीघ्र क्रियान्वित की जाएगी, लेकिन अगर incrementB भी है सिंक्रनाइज़ तो इसके लिए 'प्रतीक्षा' के लिए है incrementA से पहले, समाप्त करने के लिए incrementB अपना काम कर सकते हैं।

दोनों ही तरीकों से एक उदाहरण पर कहा जाता है - वस्तु, इस उदाहरण में यह है: काम , और 'प्रतिस्पर्धा' धागे हैं aThread और मुख्य

साथ 'प्रयास करें सिंक्रनाइज़ में' incrementB और इसके बिना और आप अलग अलग results.If देखेंगे incrementB 'है सिंक्रनाइज़ ' के रूप में अच्छी तरह से तो इसके लिए इंतजार करना पड़ता है incrementA () समाप्त करने के लिए। प्रत्येक संस्करण को कई बार चलाएं।

class LockTest implements Runnable {
    int a = 0;
    int b = 0;

    public synchronized void incrementA() {
        for (int i = 0; i < 100; i++) {
            this.a++;
            System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
        }
    }

    // Try with 'synchronized' and without it and you will see different results
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish

    // public void incrementB() {
    public synchronized void incrementB() {
        this.b++;
        System.out.println("*************** incrementB ********************");
        System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
        System.out.println("*************** incrementB ********************");
    }

    @Override
    public void run() {
        incrementA();
        System.out.println("************ incrementA completed *************");
    }
}

class LockTestMain {
    public static void main(String[] args) throws InterruptedException {
        LockTest job = new LockTest();
        Thread aThread = new Thread(job);
        aThread.setName("aThread");
        aThread.start();
        Thread.sleep(1);
        System.out.println("*************** 'main' calling metod: incrementB **********************");
        job.incrementB();
    }
}

1

जावा सिंक्रोनाइज़ेशन में, यदि कोई थ्रेड सिंक्रोनाइज़ेशन विधि में प्रवेश करना चाहता है, तो यह उस ऑब्जेक्ट के सभी सिंक्रनाइज़ किए गए तरीकों पर लॉक को अधिग्रहित करेगा न कि केवल एक सिंक्रनाइज़ेशन विधि पर जो थ्रेड उपयोग कर रहा है। तो एक धागा addA को जोड़ने () addA () और addB () दोनों पर ताला के रूप में एक ही वस्तु के साथ अन्य धागे addB निष्पादित नहीं कर सकते हैं।


0

यह इंटर्गर से बॉक्सिंग और ऑटोबॉक्सिंग के रूप में काम नहीं कर सकता है और वाइसवर्सा जेवीएम पर निर्भर है और इस बात की बहुत अधिक संभावना है कि दो अलग-अलग संख्याओं को एक ही पते पर हैशड किया जा सकता है यदि वे -128 और 127 के बीच हों।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.