Synchronized হল Java ভাষায় একটি অন্তর্নির্মিত সিঙ্ক্রোনাইজেশন প্রক্রিয়া যা কোডের ক্রিটিক্যাল সেকশনে একচেটিয়া অ্যাক্সেস নিশ্চিত করে। Oracle, 2024-এর মতে, synchronized মডিফায়ার গ্যারান্টি দেয় যে শুধুমাত্র একটি থ্রেড চিহ্নিত মেথড বা ব্লক একটি নির্দিষ্ট সময়ে নির্বাহ করতে পারে। এই প্রক্রিয়াটি মনিটরের উপর ভিত্তি করে — অপারেটিং সিস্টেমের একটি মৌলিক ধারণা যা সব জটিলতা স্তরের মাল্টিথ্রেডেড অ্যাপ্লিকেশনের সঠিক কার্যক্রম নিশ্চিত করে।
মূল বিষয়
Synchronized Java-এর একটি কীওয়ার্ড যা গ্যারান্টি দেয় যে যেকোনো সময়ে শুধুমাত্র একটি থ্রেড কোডের একটি সুরক্ষিত অংশ নির্বাহ করে, সমবর্তী অ্যাক্সেসের সময় ডেটা দূষণ প্রতিরোধ করে। এটি Java-এর প্রথম সংস্করণে আবির্ভূত হয়েছিল এবং যেকোনো স্তরের ডেভেলপারের জন্য থ্রেড নিরাপত্তা নিশ্চিত করার সবচেয়ে সহজ উপায় হিসাবে রয়ে গেছে।
synchronized মডিফায়ার দুটি কাজ সমাধান করে: পারস্পরিক বর্জন (mutual exclusion) এবং পরিবর্তনের দৃশ্যমানতা (visibility)। যখন একটি থ্রেড synchronized ব্লক থেকে বেরিয়ে আসে, তখন সমস্ত পরিবর্তন অন্য থ্রেডগুলির কাছে দৃশ্যমান হয় যারা একই অবজেক্টে সিঙ্ক্রোনাইজড ব্লকে প্রবেশ করে।
Synchronized একটি সম্পূর্ণ মেথডে বা মনিটর অবজেক্ট উল্লেখ করে একটি কোড ব্লকে প্রয়োগ করা যেতে পারে। উভয় ক্ষেত্রেই, JVM বাইটকোড স্তরে monitorenter এবং monitorexit নির্দেশ সন্নিবেশ করে।
সিঙ্ক্রোনাইজেশন ছাড়া মাল্টিথ্রেডেড অ্যাপ্লিকেশনে, রেস কন্ডিশন (race condition) ঘটে — যখন দুটি থ্রেড একই সাথে একই ডেটা পরিবর্তন করে, যা অপ্রত্যাশিত ফলাফলের দিকে নিয়ে যায়। Synchronized এই সমস্যা মোকাবেলার জন্য Java-এর প্রথম এবং প্রধান হাতিয়ার হয়ে ওঠে, যেকোনো ডেভেলপারের জন্য সহজলভ্য একটি সরল ঘোষণামূলক সিনট্যাক্স প্রদান করে।
Synchronized প্রক্রিয়া মনিটরের ধারণার উপর ভিত্তি করে — একটি উচ্চ-স্তরের সিঙ্ক্রোনাইজেশন প্রিমিটিভ যা প্রতিটি Java অবজেক্টে নির্মিত। মনিটর একটি অবজেক্টের সাথে যুক্ত হয় যখন প্রথমবার তার উপর synchronized ব্লক ব্যবহার করা হয়।
Java-তে প্রতিটি অবজেক্টের সাথে একটি সংশ্লিষ্ট মনিটর থাকে। যখন একটি থ্রেড synchronized ব্লকে প্রবেশ করে, তখন এটি অবজেক্টের মনিটর অর্জন করে। যদি মনিটর ইতিমধ্যেই অন্য থ্রেড দ্বারা ধারণ করা থাকে, তবে থ্রেডটি মুক্তি না হওয়া পর্যন্ত ব্লক হয়ে যায়। বাইটকোডে, এটি monitorenter এবং monitorexit নির্দেশের জোড়ার সাথে মিলে যায়।
JVM বিভিন্ন স্তরের মাধ্যমে synchronized অপ্টিমাইজ করে: একক-থ্রেড অ্যাক্সেসের জন্য biased locking (পক্ষপাতমূলক লকিং), কম প্রতিযোগিতার জন্য lightweight locking (হালকা লকিং), এবং OS অংশগ্রহণ সহ তীব্র প্রতিযোগিতার জন্য heavyweight locking (ভারী লকিং)। এই স্তরগুলি কোড পরিবর্তন না করেই কর্মক্ষমতা উন্নত করে।
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Synchronized একটি happens-before সম্পর্ক স্থাপন করে: synchronized ব্লক থেকে বেরিয়ে আসার আগে একটি থ্রেডের সমস্ত ক্রিয়া একই অবজেক্টে সিঙ্ক্রোনাইজড ব্লকে প্রবেশের পরে অন্য থ্রেডের কাছে দৃশ্যমান হয়। এটি কেবল পারস্পরিক বর্জনই নয়, সমস্ত থ্রেডের জন্য ডেটা সামঞ্জস্যও নিশ্চিত করে।
Java synchronized প্রয়োগের দুটি উপায় প্রদান করে: মেথড স্তরে এবং ব্লক স্তরে। তাদের মধ্যে পছন্দ কর্মক্ষমতা এবং সিঙ্ক্রোনাইজেশন গ্রানুলারিটি প্রভাবিত করে।
synchronized মডিফায়ার দিয়ে একটি মেথড চিহ্নিত করা স্বয়ংক্রিয়ভাবে এটি বর্তমান ইনস্ট্যান্স (সাধারণ মেথডের জন্য) বা Class অবজেক্টে (স্ট্যাটিক মেথডের জন্য) সিঙ্ক্রোনাইজ করে। এটি পারস্পরিক বর্জন নিশ্চিত করার সবচেয়ে সহজ উপায়, কিন্তু এটি প্রায়ই অতিরিক্ত যদি ক্রিটিক্যাল সেকশন মেথডের শুধুমাত্র একটি ছোট অংশ গঠন করে এবং বাকি কোডের সিঙ্ক্রোনাইজেশনের প্রয়োজন না হয়।
Synchronized ব্লক সঠিক নিয়ন্ত্রণ দেয়: আপনি মনিটর অবজেক্ট নির্দিষ্ট করেন এবং কোডের শুধুমাত্র প্রয়োজনীয় অংশ সিঙ্ক্রোনাইজ করেন, বাকি মেথড লকের বাইরে রেখে। এটি মনিটর ধারণ সময় হ্রাস করে এবং মাল্টিথ্রেডেড পরিবেশে সামগ্রিক অ্যাপ্লিকেশন কর্মক্ষমতা উন্নত করে, কারণ অন্যান্য থ্রেড মনিটর মুক্তির অপেক্ষা না করেই সমান্তরালভাবে সম্পর্কহীন কোড নির্বাহ করতে পারে।
class DataProcessor {
private final Object lock = new Object();
public void process() {
// ক্রিটিক্যাল সেকশনের বাইরে কোড - সিঙ্ক্রোনাইজেশন ছাড়া
prepareData()
synchronized (lock) {
// শুধুমাত্র এই ব্লক সুরক্ষিত
updateSharedState()
}
// লক ছাড়া চলতে থাকে
cleanup()
}
}
| নির্ণায়ক | Synchronized মেথড | Synchronized ব্লক |
|---|---|---|
| মনিটর | this (ইনস্ট্যান্স) বা Class | যেকোনো অবজেক্ট |
| গ্রানুলারিটি | পুরো মেথড | শুধুমাত্র প্রয়োজনীয় কোড |
| পঠনযোগ্যতা | উচ্চ | মধ্যম |
| কর্মক্ষমতা | বড় মেথডের জন্য কম | ছোট ক্রিটিক্যাল সেকশনের জন্য বেশি |
Android ডেভেলপমেন্টে, synchronized ব্যাপকভাবে SharedPreferences, ডেটাবেস অ্যাক্সেস এবং UI উপাদান রক্ষার জন্য ব্যবহৃত হয়। তবে, ইন্টারফেস জমে যাওয়ার ঝুঁকির কারণে মূল থ্রেডে এর ব্যবহার কঠোরভাবে নিরুৎসাহিত করা হয়।
Android-এ SharedPreferences মৌলিক থ্রেড নিরাপত্তা প্রদান করে, কিন্তু Editor-এর মাধ্যমে একাধিক থ্রেড থেকে সম্পাদনা করার সময় বাহ্যিক সিঙ্ক্রোনাইজেশনের প্রয়োজন হতে পারে। একটি পৃথক লক অবজেক্ট সহ synchronized ব্লক পরিবর্তনের সামঞ্জস্য নিশ্চিত করে।
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
Android-এ synchronized-এর প্রধান সীমাবদ্ধতা হল থ্রেড ব্লকিং। Mutex সহ coroutines-এর বিপরীতে, synchronized সিস্টেম থ্রেড সম্পূর্ণরূপে ব্লক করে। মূল থ্রেডে, এটি ANR সৃষ্টি করে। আধুনিক Android ডেভেলপমেন্টে, synchronized-কে coroutines (suspend Mutex) বা পারমাণবিক প্রকার (AtomicInteger) দিয়ে প্রতিস্থাপন করার সুপারিশ করা হয়।
আধুনিক Java এবং Kotlin synchronized-এর বেশ কয়েকটি বিকল্প প্রদান করে, প্রতিটি একই সমস্যা কম সীমাবদ্ধতা বা ভাল কর্মক্ষমতা সহ সমাধান করে।
Lock ইন্টারফেস ReentrantLock এবং ReadWriteLock বাস্তবায়ন সহ টাইমআউট, বাধাযোগ্য অপেক্ষা এবং একাধিক Condition কিউ প্রদান করে। এটি synchronized-এর চেয়ে বেশি নমনীয় কিন্তু finally-তে স্পষ্ট মুক্তি প্রয়োজন, যা unlock ভুলে গেলে ত্রুটির ঝুঁকি বাড়ায়।
AtomicInteger, AtomicLong, AtomicReference এবং অন্যান্য ক্লাস CAS (Compare-And-Swap) ভিত্তিক Lock-Free অ্যালগরিদম ব্যবহার করে। এগুলি মধ্যম প্রতিযোগিতার পরিস্থিতিতে synchronized-এর চেয়ে উল্লেখযোগ্যভাবে দ্রুত কারণ এগুলি থ্রেড ব্লক করে না বরং OS কার্নেল কনটেক্সট সুইচিংয়ের প্রয়োজন ছাড়াই আশাবাদী পুনঃচেষ্টা করে।
ThreadLocal একটি বিকল্প পদ্ধতি প্রদান করে: প্রতিটি ThreadLocal চলক একটি থ্রেডের মধ্যে বিচ্ছিন্ন এবং পড়া ও লেখার জন্য সিঙ্ক্রোনাইজেশনের প্রয়োজন হয় না। এটি সেই ডেটার জন্য synchronized-এর প্রয়োজনীয়তা সম্পূর্ণরূপে দূর করে যা থ্রেডগুলির মধ্যে ভাগ করা উচিত নয়। ThreadLocal ফ্রেমওয়ার্কে (Spring, Hibernate) লেনদেন প্রসঙ্গ এবং সেশন সংরক্ষণের জন্য সক্রিয়ভাবে ব্যবহৃত হয়।
Android-এর জন্য Kotlin প্রকল্পে, synchronized-এর বিকল্প হল kotlinx.coroutines থেকে Mutex। এটি অপারেটিং সিস্টেম থ্রেড ব্লক করে না বরং লক মুক্তি না হওয়া পর্যন্ত coroutine স্থগিত করে — এটি পুল থ্রেডের কার্যকর ব্যবহার এবং সম্পদ মুক্তির দীর্ঘ অপেক্ষার সময় ANR এড়াতে অনুমতি দেয়।
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
Synchronized-এর কর্মক্ষমতা সাম্প্রতিক Java সংস্করণগুলিতে উল্লেখযোগ্যভাবে পরিবর্তিত হয়েছে। আগে এটি একটি “ভারী” প্রক্রিয়া হিসাবে বিবেচিত হত, কিন্তু আধুনিক JVM উন্নত JIT কম্পাইলার অপ্টিমাইজেশনের মাধ্যমে বেশিরভাগ ওভারহেড দূর করেছে। চলুন বিস্তারিত দেখি কীভাবে ভার্চুয়াল মেশিন রানটাইমে সিঙ্ক্রোনাইজড কোড ত্বরান্বিত করে।
JVM JIT কম্পাইলার বেশ কয়েকটি অপ্টিমাইজেশন প্রয়োগ করে: biased locking সিঙ্ক্রোনাইজেশন দূর করে যদি লক সর্বদা একই থ্রেড দ্বারা অর্জিত হয়; lock coarsening সংলগ্ন synchronized ব্লকগুলিকে একত্রিত করে; lock elimination সিঙ্ক্রোনাইজেশন সরিয়ে দেয় যদি অবজেক্ট শুধুমাত্র একটি থ্রেডের কাছে অ্যাক্সেসযোগ্য হয়। এই অপ্টিমাইজেশনগুলি কম প্রতিযোগিতায় synchronized-কে কার্যত বিনামূল্যে করে তোলে।
JVM প্রতিটি অবজেক্টের জন্য প্রতিযোগিতার স্তর নির্ধারণ করে: যখন কোনও প্রতিযোগিতা নেই, তখন biased locking সক্রিয় হয়; যখন দ্বিতীয় থ্রেড আবির্ভূত হয়, লক স্পিন-অপেক্ষা সহ lightweight মোডে রূপান্তরিত হয়; এবং শুধুমাত্র দীর্ঘ অপেক্ষার সময় এটি সিস্টেম মিউটেক্স সহ heavyweight-এ রূপান্তরিত হয়। এই রূপান্তর স্বয়ংক্রিয়ভাবে ঘটে এবং ডেভেলপারকে ম্যানুয়ালি কৌশল বেছে নেওয়ার প্রয়োজন হয় না।
আধুনিক বেঞ্চমার্কে (Java 17+), synchronized কম এবং মধ্যম প্রতিযোগিতায় ReentrantLock-এর সাথে তুলনীয় কর্মক্ষমতা দেখায়। উচ্চ প্রতিযোগিতায়, টাইমআউট এবং ইন্টারাপ্ট সমর্থন সহ আরও দক্ষ অপেক্ষা কিউর কারণে Lock-এর সুবিধা থাকতে পারে। উচ্চ-লোড সিস্টেমের জন্য যেখানে প্রতিযোগিতা ধ্রুবক, fair মোড সহ ReentrantLock আরও অনুমানযোগ্য আচরণ প্রদান করে।
পারমাণবিক ক্লাস (AtomicInteger, AtomicReference) CAS-ভিত্তিক Lock-Free বাস্তবায়নের কারণে সরল কাউন্টার এবং ফ্ল্যাগের জন্য দ্রুততম থাকে। এগুলি থ্রেড ব্লক করে না — সংঘর্ষে, অপারেশনটি কেবল একটি লুপে পুনরায় চেষ্টা করে। এটি 4-8 থ্রেডের সাথে কাউন্টার বৃদ্ধি ক্রিয়াকলাপে synchronized-এর তুলনায় 3-5 গুণ কর্মক্ষমতা লাভ দেয়।
সচরাচর জিজ্ঞাসা
Synchronized পারস্পরিক বর্জন এবং দৃশ্যমানতা উভয়ই প্রদান করে। Volatile শুধুমাত্র পরিবর্তনের দৃশ্যমানতা নিশ্চিত করে — volatile ভেরিয়েবলে লেখা সমস্ত থ্রেডের কাছে দৃশ্যমান কিন্তু একসাথে পরিবর্তন প্রতিরোধ করে না, অর্থাৎ এটি রেস কন্ডিশন থেকে রক্ষা করে না।
হ্যাঁ, বিভিন্ন মনিটর ক্রম সহ নেস্টেড সিঙ্ক্রোনাইজেশনে deadlock সম্ভব। উদাহরণস্বরূপ, একটি থ্রেড synchronized(a) { synchronized(b) } কল করে, while অন্যটি synchronized(b) { synchronized(a) } কল করে। নেস্টেড synchronized ব্লক এড়িয়ে চলুন বা একটি সামঞ্জস্যপূর্ণ মনিটর ক্রম নির্ধারণ করুন।
মনিটর হল একটি সিঙ্ক্রোনাইজেশন প্রক্রিয়া যা প্রতিটি Java অবজেক্টের সাথে যুক্ত। এটি গ্যারান্টি দেয় যে শুধুমাত্র একটি থ্রেড সেই অবজেক্টে synchronized কোড নির্বাহ করে। মনিটরে একটি লক, একটি অপেক্ষা কিউ এবং wait/notify-এর মাধ্যমে বিজ্ঞপ্তির জন্য অপেক্ষারত থ্রেডের একটি পুল অন্তর্ভুক্ত থাকে।
আধুনিক Java সংস্করণে (17+), synchronized JIT অপ্টিমাইজেশন (biased locking, lock coarsening) এর কারণে কর্মক্ষমতায় Lock-এর থেকে পিছিয়ে নেই। Lock গতির জন্য নয় বরং অতিরিক্ত ক্ষমতার জন্য পছন্দ করা হয়: টাইমআউট, বাধাযোগ্য অপেক্ষা এবং একাধিক Condition কিউ।
একটি স্ট্যাটিক synchronized মেথড প্রদত্ত ক্লাসের Class অবজেক্টের মনিটর ব্যবহার করে, ইনস্ট্যান্সের নয়। এর মানে হল সিঙ্ক্রোনাইজেশন ক্লাসের সমস্ত ইনস্ট্যান্স জুড়ে প্রযোজ্য। নন-স্ট্যাটিক এবং স্ট্যাটিক synchronized মেথডগুলি বিভিন্ন মনিটর ব্যবহার করে এবং একে অপরকে ব্লক করে না।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন