Lock ایک سنکرونائزیشن میکانزم ہے جو ملٹی تھریڈڈ ایپلی کیشنز میں کوڈ کے اہم حصوں تک خصوصی رسائی فراہم کرتا ہے۔ Oracle، 2024 کے مطابق، Lock انٹرفیس روایتی synchronized بلاکس کے مقابلے میں زیادہ لچکدار سنکرونائزیشن کنٹرول فراہم کرتا ہے، جس میں ٹائم آؤٹ کے ساتھ حصول کی کوششیں اور متعدد قطاروں کی معاونت شامل ہے۔
اہم نکات
Lock java.util.concurrent.locks پیکج کا ایک انٹرفیس ہے جو ڈیٹا تک رسائی کو ہم آہنگ کرنے کے لیے واضح لاک اور انلاک آپریشنز فراہم کرتا ہے۔ synchronized کے برعکس، Lock ڈویلپر کو لاکنگ میکانزم پر مکمل کنٹرول دیتا ہے۔
Lock انٹرفیس Java 5 میں بلٹ ان synchronized میکانزم کے متبادل کے طور پر متعارف کرایا گیا تھا۔ اہم طریقے ہیں lock، unlock، tryLock اور lockInterruptibly۔ لاک ملٹی تھریڈڈ ماحول میں محفوظ ڈیٹا تک رسائی کو منظم کرنے میں مدد کرتے ہیں، ریس کنڈیشن اور ڈیٹا کرپشن کو روکتے ہیں۔
synchronized پر Lock کا بنیادی فائدہ لچک ہے۔ ڈویلپر ٹائم آؤٹ کے ساتھ لاک حاصل کرنے کی کوشش کر سکتا ہے، بغیر بلاک ہوئے اس کی دستیابی چیک کر سکتا ہے، یا مختلف ترجیحات کے ساتھ متعدد قطاریں منظم کر سکتا ہے۔
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+ ایکٹرز کے ذریعے جدید سنکرونائزیشن میکانزم فراہم کرتا ہے، جو خود بخود حالت کی حفاظت کرتے ہیں۔
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 میں لاک جاری کرنا ہے۔ اس سے کوئی فرق نہیں پڑتا کہ اہم حصہ کامیابی سے مکمل ہوتا ہے یا مستثنیٰ پھینکتا ہے، لاک ضرور جاری ہونا چاہیے۔ یہ یقینی بناتا ہے کہ ایک غلطی کی وجہ سے دوسرے تھریڈ ہمیشہ کے لیے بلاک نہ ہوں۔ Kotlin میں، یہ پیٹرن withLock ایکسٹینشن کے ذریعے خوبصورتی سے حل ہوتا ہے۔
اہم حصہ جتنا ممکن ہو اتنا چھوٹا ہونا چاہیے۔ لاک کے اندر کبھی بھی I/O، نیٹ ورک درخواستیں یا طویل حساب نہ کریں۔ اگر آپ کو سرور سے ڈیٹا پڑھنے کی ضرورت ہے، پہلے ڈیٹا حاصل کریں، پھر صرف مشترکہ حالت کو اپ ڈیٹ کرنے کے لیے لاک حاصل کریں۔ یہ مسابقت کو کم کرتا ہے اور سسٹم تھروپوٹ کو بہتر بناتا ہے۔
متعدد لاک کے ساتھ کام کرتے وقت ڈیڈلاک کو روکنے کے لیے، پورے پروجیکٹ میں عالمی لاک ترتیب قائم کریں۔ اگر پہلے lockA حاصل ہوتا ہے، پھر lockB — کسی بھی معکوس ترتیب کو کوڈ ریویو قواعد کے ذریعے ممنوع ہونا چاہیے۔ خودکار تصدیق کے لیے SpotBugs اور IntelliJ Inspections جیسے جامد تجزیہ کار استعمال کریں۔
اکثر پوچھے گئے سوالات
Lock ایک واضح انٹرفیس ہے جس میں ٹائم آؤٹ اور قابل مداخلت انتظار کی حمایت ہے۔ synchronized خود بخود مانیٹر حاصل اور جاری کرتا ہے، لیکن tryLock، lockInterruptibly یا متعدد Conditions استعمال کرنے کی اجازت نہیں دیتا۔ Lock زیادہ لچکدار ہے لیکن finally میں دستی جاری کرنے کی ضرورت ہے۔
منصفانہ لاک FIFO ترتیب کی ضمانت دیتا ہے: جو تھریڈ سب سے زیادہ دیر انتظار کر رہا ہے، اسے پہلے لاک ملتا ہے۔ غیر منصفانہ لاک منتظر تھریڈ سے پہلے نئے تھریڈ کو رسائی دے سکتا ہے، جو تھروپوٹ بڑھاتا ہے لیکن منتظر تھریڈ کی بھوک کا سبب بن سکتا ہے۔
تمام لاک حاصل کرنے کے لیے مقررہ ترتیب پر عمل کریں، غیر مشروط lock() کے بجائے ٹائم آؤٹ کے ساتھ tryLock استعمال کریں، اور ایک ساتھ رکھے گئے لاک کی تعداد کم سے کم کریں۔ Lock-Free ڈیٹا ڈھانچے کا استعمال بھی ڈیڈلاک کے خطرے کو کم کرتا ہے۔
Condition Lock کے لیے wait/notify کا مشابہ ہے، جو متعدد آزاد قطاروں کی اجازت دیتا ہے۔ ہر newCondition() کال ایک علیحدہ قطار بناتی ہے، جو synchronized کی ایک قطار کے مقابلے میں تھریڈ بیداری پر زیادہ قطعی کنٹرول فراہم کرتی ہے۔
coroutines والے Android کے لیے، kotlinx.coroutines سے Mutex استعمال کریں — یہ تھریڈ کو بلاک کرنے کے بجائے coroutine کو معطل کرتا ہے۔ Swift 5.7+ والے iOS کے لیے، ایکٹرز ترجیح دی جاتی ہیں جو خود بخود حالت تک رسائی کو ہم آہنگ کرتے ہیں۔ ReentrantLock کو پرانے کوڈ اور نچلی سطح کے منظرناموں کے لیے چھوڑ دیں۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں