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 থেকে করুটিন এবং Mutex পছন্দনীয়, যা থ্রেড ব্লক করার পরিবর্তে করুটিনকে সাসপেন্ড করে।
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 ডেটা স্ট্রাকচার ব্যবহারও ডেডলকের ঝুঁকি কমায়।
Condition হল Lock-এর জন্য wait/notify-এর অনুরূপ, যা একাধিক স্বাধীন অপেক্ষমাণ সারি সক্ষম করে। প্রতিটি newCondition() কল একটি পৃথক সারি তৈরি করে, যা synchronized-এর একক সারির তুলনায় থ্রেড জাগরণের উপর আরও সঠিক নিয়ন্ত্রণ প্রদান করে।
করুটিন সহ Android-এর জন্য, kotlinx.coroutines থেকে Mutex ব্যবহার করুন — এটি থ্রেড ব্লক করার পরিবর্তে করুটিনকে সাসপেন্ড করে। Swift 5.7+ সহ iOS-এর জন্য, actors পছন্দনীয় যা স্বয়ংক্রিয়ভাবে অবস্থা অ্যাক্সেস সিঙ্ক্রোনাইজ করে। ReentrantLock লিগেসি কোড এবং নিম্ন-স্তরের পরিস্থিতির জন্য সংরক্ষিত রাখুন।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন