Lock एक सिंक्रोनाइज़ेशन तंत्र है जो मल्टीथ्रेडेड एप्लिकेशन में कोड के क्रिटिकल सेक्शन तक एक्सक्लूसिव एक्सेस प्रदान करता है। Oracle, 2024 के अनुसार, Lock इंटरफ़ेस पारंपरिक synchronized ब्लॉक की तुलना में अधिक लचीला सिंक्रोनाइज़ेशन नियंत्रण प्रदान करता है, जिसमें टाइमआउट के साथ अधिग्रहण प्रयास और कई प्रतीक्षा कतारों का समर्थन शामिल है।
मुख्य बिंदु
Lock java.util.concurrent.locks पैकेज का एक इंटरफ़ेस है जो डेटा एक्सेस को सिंक्रोनाइज़ करने के लिए स्पष्ट लॉक और अनलॉक संचालन प्रदान करता है। synchronized के विपरीत, Lock डेवलपर को लॉकिंग तंत्र पर पूर्ण नियंत्रण देता है।
Lock इंटरफ़ेस Java 5 में अंतर्निहित synchronized तंत्र के विकल्प के रूप में पेश किया गया था। मुख्य विधियाँ हैं lock, unlock, tryLock और lockInterruptibly। लॉक मल्टीथ्रेडेड वातावरण में सुरक्षित डेटा एक्सेस व्यवस्थित करने में मदद करते हैं, रेस कंडीशन और डेटा भ्रष्टाचार को रोकते हैं।
Lock का synchronized पर मुख्य लाभ लचीलापन है। डेवलपर टाइमआउट के साथ लॉक प्राप्त करने का प्रयास कर सकता है, बिना ब्लॉक हुए इसकी उपलब्धता जाँच सकता है, या विभिन्न प्राथमिकताओं के साथ कई प्रतीक्षा कतारें व्यवस्थित कर सकता है।
Java 5 में Lock इंटरफ़ेस आने से पहले, सिंक्रोनाइज़ेशन का एकमात्र तरीका synchronized था, जो सीमाओं से ग्रस्त था: कोई टाइमआउट नहीं, कोई इंटरप्टिबल प्रतीक्षा नहीं, और एकल कतार। Doug Lea ने java.util.concurrent पैकेज डिज़ाइन किया, जिसमें Lock को एक मौलिक बिल्डिंग ब्लॉक के रूप में शामिल किया।
एक लॉक आंतरिक स्थिति फ़्लैग और प्रतीक्षा कतार के माध्यम से एक्सेस प्रबंधित करता है। जब कोई थ्रेड lock() कॉल करता है, तंत्र जाँचता है कि लॉक खाली है या नहीं और या तो उसे प्राप्त करता है या थ्रेड को कतार में तब तक रखता है जब तक वह जारी न हो जाए।
किसी भी लॉक के केंद्र में एक परमाणु तुलना-और-सेट (CAS) संक्रिया होती है। जब lock() कॉल किया जाता है, थ्रेड परमाणु रूप से व्यस्त फ़्लैग सेट करने का प्रयास करता है। यदि फ़्लैग पहले से सेट है, थ्रेड ब्लॉक हो जाता है। unlock() पर, फ़्लैग साफ़ हो जाता है और एक प्रतीक्षारत थ्रेड जागृत होता है।
import java.util.concurrent.locks.ReentrantLock
val lock = ReentrantLock()
fun performTask() {
lock.lock()
try {
// क्रिटिकल सेक्शन
println("थ्रेड ${Thread.currentThread().name} काम कर रहा है")
} finally {
lock.unlock()
}
}
ReentrantLock आंतरिक रूप से एक दोगुनी लिंक्ड सूची (CLH लॉक कतार) का उपयोग करता है जहाँ प्रत्येक प्रतीक्षारत थ्रेड को एक नोड द्वारा दर्शाया जाता है। जब लॉक जारी होता है, कतार का हेड नोड जागृत होता है। फेयर मोड FIFO क्रम की गारंटी देता है, जबकि अनफेयर मोड थ्रूपुट बढ़ाने के लिए एक नए थ्रेड को प्रतीक्षारत थ्रेड से पहले लॉक प्राप्त करने की अनुमति देता है।
आधुनिक Java पारिस्थितिकी तंत्र में, लॉक के कई कार्यान्वयन हैं, प्रत्येक विशिष्ट परिदृश्यों के लिए अनुकूलित। सही लॉक चुनना सीधे मल्टीथ्रेडेड एप्लिकेशन के प्रदर्शन और विश्वसनीयता को प्रभावित करता है।
ReentrantLock मूल और सबसे अधिक उपयोग किया जाने वाला Lock कार्यान्वयन है। यह उसी थ्रेड द्वारा पुनः अधिग्रहण का समर्थन करता है: यदि कोई थ्रेड पहले से लॉक रखता है, तो lock() को फिर से कॉल करना उसे ब्लॉक नहीं करता है। यह पुनरावर्ती कॉल में deadlock को रोकता है।
ReadWriteLock लॉक को दो मोड में अलग करता है: रीड और राइट। कई थ्रेड एक साथ रीड लॉक रख सकते हैं, लेकिन राइट के लिए एक्सक्लूसिव एक्सेस की आवश्यकता होती है। यह बार-बार पढ़ने और कम लिखने पर प्रदर्शन में काफी सुधार करता है।
StampedLock नवीनतम कार्यान्वयन है, जो Java 8 में पेश किया गया। यह तीन मोड का समर्थन करता है: राइट, रीड और ऑप्टिमिस्टिक रीड। ऑप्टिमिस्टिक रीड अन्य थ्रेड को ब्लॉक नहीं करता है और पढ़ने के बाद डेटा को मान्य करता है, जो ReadWriteLock की तुलना में 10-20% प्रदर्शन लाभ प्रदान करता है।
| लॉक | Java संस्करण | मोड | प्रदर्शन |
|---|---|---|---|
| ReentrantLock | Java 5 | एक्सक्लूसिव | उच्च |
| ReadWriteLock | Java 5 | रीड + राइट | मध्यम |
| StampedLock | Java 8 | रीड + राइट + ऑप्टिमिस्टिक | बहुत उच्च |
ReentrantLock सबसे लोकप्रिय Lock कार्यान्वयन है, जो synchronized में उपलब्ध नहीं होने वाली कई सुविधाएँ प्रदान करता है। इसकी विशेषताओं को समझना प्रभावी मल्टीथ्रेडिंग कार्य के लिए आवश्यक है।
ReentrantLock कंस्ट्रक्टर एक fair पैरामीटर स्वीकार करता है। जब true होता है, लॉक FIFO क्रम की गारंटी देता है; जब false होता है, एक नया थ्रेड प्रतीक्षारत थ्रेड से पहले लॉक प्राप्त कर सकता है। फेयर मोड स्टार्वेशन को रोकता है लेकिन कतार रखरखाव के ओवरहेड के कारण 10-20% थ्रूपुट कम करता है।
synchronized के विपरीत, ReentrantLock टाइमआउट के साथ tryLock का समर्थन करता है। यदि निर्दिष्ट समय के भीतर लॉक प्राप्त नहीं किया जा सकता, थ्रेड अनिश्चित काल तक ब्लॉक होने के बजाय निष्पादन जारी रखता है। lockInterruptibly विधि Thread.interrupt() के माध्यम से प्रतीक्षारत थ्रेड को बाधित करने की अनुमति देती है।
val lock = ReentrantLock()
fun tryTask() {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
println("लॉक प्राप्त हुआ")
} finally {
lock.unlock()
}
} else {
println("लॉक प्राप्त करने में विफल")
}
}
ReentrantLock newCondition() विधि के माध्यम से कई कंडीशन वेरिएबल का समर्थन करता है। प्रत्येक Condition की अपनी प्रतीक्षा कतार होती है, जो जटिल जागरण परिदृश्यों को सक्षम करती है। await() और signal() विधियों ने synchronized ब्लॉक के wait() और notify() को बदल दिया, लेकिन कई कतारों के समर्थन के साथ।
ReadWriteLock और StampedLock उस एक्सेस के ऑप्टिमाइज़ेशन को संबोधित करते हैं जब रीड राइट पर हावी होती है। वे उन परिदृश्यों में ReentrantLock की तुलना में काफी अधिक कुशल हैं जहाँ राइट की तुलना में रीड अधिक बार होती है।
ReadWriteLock इंटरफ़ेस में दो विधियाँ हैं: readLock() और writeLock()। रीड लॉक को कई थ्रेड एक साथ रख सकते हैं, जबकि राइट लॉक एक्सक्लूसिव है। एक विशिष्ट उदाहरण थ्रेड-सेफ कैश है: कई थ्रेड डेटा पढ़ते हैं जबकि केवल एक समय-समय पर इसे अपडेट करता है।
class SafeCache<K, V> {
private val map = mutableMapOf<K, V>()
private val rwLock = ReentrantReadWriteLock()
fun get(key: K): V? {
rwLock.readLock().lock()
return try { map[key] } finally { rwLock.readLock().unlock() }
}
fun put(key: K, value: V) {
rwLock.writeLock().lock()
return try { map[key] = value } finally { rwLock.writeLock().unlock() }
}
}
StampedLock एक तीसरा मोड जोड़ता है — tryOptimisticRead। यह मोड अन्य थ्रेड को ब्लॉक नहीं करता बल्कि केवल स्थिति का स्टैम्प रिकॉर्ड करता है। पढ़ने के बाद, डेवलपर यह जाँचने के लिए validate(stamp) कॉल करता है कि पढ़ने के दौरान डेटा बदला तो नहीं। यदि डेटा बदल गया, तो ऑपरेशन दोहराया जाना चाहिए।
मोबाइल एप्लिकेशन में, लॉक का उपयोग थ्रेड के बीच साझा डेटा तक पहुँच को समन्वयित करने के लिए किया जाता है। हालाँकि, सीमित डिवाइस संसाधनों और UI प्रतिक्रिया बनाए रखने की आवश्यकता के कारण उनके उपयोग में विशेष सावधानी की आवश्यकता होती है।
Android पर, ReentrantLock Room, कैश और फ़ाइलों के साथ काम करते समय उपयोगी है। याद रखना महत्वपूर्ण है: मुख्य थ्रेड पर कभी लॉक प्राप्त न करें। एसिंक्रोनस कोड के लिए, kotlinx.coroutines से coroutines और Mutex बेहतर हैं, जो थ्रेड को ब्लॉक करने के बजाय coroutine को सस्पेंड करते हैं।
iOS में, मानक NSLock का कम उपयोग किया जाता है — डेवलपर बैरियर फ़्लैग वाली DispatchQueue या os_unfair_lock पसंद करते हैं। Swift 5.7+ actors के माध्यम से आधुनिक सिंक्रोनाइज़ेशन तंत्र प्रदान करता है, जो स्वचालित रूप से स्थिति की रक्षा करते हैं।
import Foundation
actor DataStore {
private var items: [String] = []
func add(_ item: String) {
items.append(item)
}
func getAll() -> [String] {
items
}
}
डेडलॉक से बचने के लिए, पूरे प्रोजेक्ट में एक सुसंगत लॉक क्रम का पालन करें। जहाँ भी लंबे समय तक ब्लॉक होना संभव हो, lock() के बजाय टाइमआउट के साथ tryLock का उपयोग करें। पारंपरिक लॉक के बजाय Lock-Free एल्गोरिदम (AtomicReference, ConcurrentHashMap) का उपयोग करने पर विचार करें।
Lock का उपयोग करने के लिए अनुशासन और कई नियमों के पालन की आवश्यकता होती है जो डेडलॉक और प्रदर्शन गिरावट को रोकते हैं। java.util.concurrent पैकेज के 20 वर्षों के उपयोग में Java समुदाय द्वारा ये अभ्यास विकसित किए गए हैं।
सबसे महत्वपूर्ण पैटर्न finally में lock है। चाहे क्रिटिकल सेक्शन सफलतापूर्वक पूरा हो या अपवाद फेंके, लॉक को जारी किया जाना चाहिए। यह सुनिश्चित करता है कि एक त्रुटि के कारण अन्य थ्रेड हमेशा के लिए ब्लॉक न हों। Kotlin में, यह पैटर्न withLock एक्सटेंशन के माध्यम से सुरुचिपूर्ण ढंग से हल किया जाता है।
क्रिटिकल सेक्शन जितना संभव हो उतना छोटा होना चाहिए। लॉक के अंदर कभी भी I/O, नेटवर्क अनुरोध या लंबी गणना न करें। यदि आपको सर्वर से डेटा पढ़ने की आवश्यकता है, पहले इसे प्राप्त करें, फिर केवल साझा स्थिति को अपडेट करने के लिए लॉक प्राप्त करें। यह प्रतिस्पर्धा को कम करता है और सिस्टम थ्रूपुट में सुधार करता है।
कई लॉक के साथ काम करते समय डेडलॉक को रोकने के लिए, पूरे प्रोजेक्ट में एक वैश्विक लॉक क्रम स्थापित करें। यदि पहले lockA प्राप्त होता है, फिर lockB — किसी भी विपरीत अनुक्रम को कोड समीक्षा नियमों द्वारा प्रतिबंधित किया जाना चाहिए। स्वचालित सत्यापन के लिए SpotBugs और IntelliJ Inspections जैसे स्थैतिक विश्लेषक का उपयोग करें।
अक्सर पूछे जाने वाले प्रश्न
Lock एक स्पष्ट इंटरफ़ेस है जिसमें टाइमआउट और इंटरप्टिबल प्रतीक्षा समर्थन है। synchronized स्वचालित रूप से मॉनिटर प्राप्त और जारी करता है, लेकिन tryLock, lockInterruptibly या कई Conditions का उपयोग करने की अनुमति नहीं देता है। Lock अधिक लचीला है लेकिन finally में मैन्युअल रिलीज़ की आवश्यकता होती है।
फेयर लॉक FIFO क्रम की गारंटी देता है: जो थ्रेड सबसे लंबे समय से प्रतीक्षा कर रहा है, उसे पहले लॉक मिलता है। अनफेयर लॉक प्रतीक्षारत थ्रेड से पहले एक नए थ्रेड को एक्सेस दे सकता है, जो थ्रूपुट बढ़ाता है लेकिन प्रतीक्षारत थ्रेड के लिए स्टार्वेशन का कारण बन सकता है।
सभी लॉक प्राप्त करने के लिए एक निश्चित क्रम का पालन करें, बिना शर्त lock() के बजाय टाइमआउट के साथ tryLock का उपयोग करें, और एक साथ रखे गए लॉक की संख्या कम करें। Lock-Free डेटा संरचनाओं का उपयोग भी deadlock के जोखिम को कम करता है।
Condition Lock के लिए wait/notify का एनालॉग है, जो कई स्वतंत्र प्रतीक्षा कतारों की अनुमति देता है। प्रत्येक newCondition() कॉल एक अलग कतार बनाता है, जो synchronized की एकल कतार की तुलना में थ्रेड जागरण पर अधिक सटीक नियंत्रण प्रदान करता है।
coroutines वाले Android के लिए, kotlinx.coroutines से Mutex का उपयोग करें — यह थ्रेड को ब्लॉक करने के बजाय coroutine को सस्पेंड करता है। Swift 5.7+ वाले iOS के लिए, actors बेहतर हैं जो स्वचालित रूप से स्थिति एक्सेस को सिंक्रोनाइज़ करते हैं। ReentrantLock को लीगेसी कोड और निम्न-स्तरीय परिदृश्यों के लिए छोड़ दें।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें