जावा में «Synchronized»: यह क्या है और उदाहरणों के साथ कैसे काम करता है

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

«synchronized का क्या मतलब है?» — एक सवाल जो हर दूसरे जूनियर को परेशान करता है

\n\n

यदि आपने कभी जावा में मल्टीथ्रेडेड एप्लिकेशन लिखा है, तो आपने निश्चित रूप से ऐसी स्थिति का सामना किया होगा: दो थ्रेड एक साथ एक वेरिएबल बदलते हैं, और परिणामस्वरूप — बग जो सौ रन में एक बार दिखाई देते हैं। परिचित लगता है? इसी के लिए synchronized कीवर्ड मौजूद है। लेकिन यह वास्तव में क्या करता है? यह «हुड के नीचे» कैसे काम करता है? और सबसे महत्वपूर्ण बात — इसका उपयोग कब करें, और कब इसके बिना काम चलाना बेहतर है?

\n\n

इस लेख में हम केवल दस्तावेज़ीकरण को दोबारा नहीं बताएंगे। हम Stack Overflow के एक लोकप्रिय प्रश्न (वही जिसने हज़ारों व्यूज़ जुटाए) को समझेंगे, इसे शैक्षिक प्रारूप में ढालेंगे और दृष्टिकोणों की तुलना दिखाएंगे: विधि स्तर पर सिंक्रोनाइज़ेशन, ब्लॉक स्तर पर, और java.util.concurrent के विकल्प। चलिए शुरू करते हैं!

\n\n

समस्या: थ्रेड रेस (Race Condition)

\n\n

कल्पना करें: आपके पास एक काउंटर है जिसे दो थ्रेड बढ़ा रहे हैं। कोड सरल है, लेकिन परिणाम अप्रत्याशित है।

\n\n
public class Counter {\n    private int count = 0;\n\n    public void increment() {\n        count++;\n    }\n\n    public int getCount() {\n        return count;\n    }\n}
\n\n

हम दो थ्रेड चलाते हैं, प्रत्येक 1000 इंक्रीमेंट करता है। लगता है 2000 होना चाहिए। लेकिन वास्तव में — 1987, 1993, या 1765 भी। क्यों? क्योंकि count++ ऑपरेशन एटॉमिक नहीं है। यह तीन चरणों में होता है: पढ़ना → बढ़ाना → लिखना। थ्रेड «ओवरलैप» होते हैं, और एक बदलाव खो जाता है।

\n\n

यही है race condition — रेस की स्थिति। और synchronized इससे निपटने के मुख्य उपकरणों में से एक है।

\n\n

synchronized क्या करता है? हुड के नीचे झाँकते हैं

\n\n

जावा में synchronized कीवर्ड आपसी बहिष्कार (mutex) और थ्रेड्स के बीच बदलावों की दृश्यता सुनिश्चित करता है। जब कोई थ्रेड synchronized ब्लॉक में प्रवेश करता है, तो वह ऑब्जेक्ट (या क्लास) के मॉनिटर को पकड़ लेता है। उसी मॉनिटर पर किसी भी synchronized ब्लॉक में प्रवेश करने का प्रयास करने वाले अन्य थ्रेड उसके मुक्त होने तक ब्लॉक हो जाते हैं।

\n\n

महत्वपूर्ण: synchronized केवल एक मॉनिटर के साथ काम करता है। यदि आपके पास दो अलग-अलग ऑब्जेक्ट हैं — सिंक्रोनाइज़ेशन काम नहीं करेगा।

\n\n

सिंक्रोनाइज़ेशन के दो स्तर

\n
    \n
  • सिंक्रोनाइज़्ड विधि — पूरी विधि को लॉक करता है, ऑब्जेक्ट के मॉनिटर (this) का उपयोग करता है।
  • \n
  • सिंक्रोनाइज़्ड ब्लॉक — केवल कोड के एक हिस्से को लॉक करता है, मॉनिटर के रूप में किसी भी ऑब्जेक्ट को निर्दिष्ट किया जा सकता है।
  • \n
\n\n

आइए व्यवहार में उनकी तुलना करें।

\n\n

दृष्टिकोणों की तुलना: विधि बनाम ब्लॉक

\n\n

1. सिंक्रोनाइज़्ड विधि

\n\n

सबसे आसान तरीका — विधि के सिग्नेचर में synchronized जोड़ना।

\n\n
public class SafeCounter {\n    private int count = 0;\n\n    public synchronized void increment() {\n        count++;\n    }\n\n    public synchronized int getCount() {\n        return count;\n    }\n}
\n\n

लाभ:

\n
    \n
  • न्यूनतम कोड, पढ़ने में आसान।
  • \n
  • गारंटी देता है कि पूरी विधि एक एटॉमिक ऑपरेशन है।
  • \n
\n\n

नुकसान:

\n
    \n
  • «भारी» लॉकिंग — भले ही विधि के अंदर केवल एक पंक्ति हो, थ्रेड पूरे निष्पादन समय के लिए ब्लॉक हो जाता है।
  • \n
  • प्रदर्शन कम करता है, यदि विधि लंबी है या बार-बार कॉल की जाती है।
  • \n
  • लचीला नहीं — केवल तर्क के एक हिस्से को सिंक्रोनाइज़ नहीं किया जा सकता।
  • \n
\n\n

2. सिंक्रोनाइज़्ड ब्लॉक

\n\n

अधिक सूक्ष्म नियंत्रण। आप स्वयं तय करते हैं कि मॉनिटर के रूप में किस ऑब्जेक्ट का उपयोग करना है।

\n\n
public class SafeCounterWithBlock {\n    private int count = 0;\n    private final Object lock = new Object();\n\n    public void increment() {\n        synchronized (lock) {\n            count++;\n        }\n    }\n\n    public int getCount() {\n        synchronized (lock) {\n            return count;\n        }\n    }\n}

ब्लॉग

पुस्तक अनुशंसाएँ