Thread States & Synchronization: Java Multi-threading සිංහලෙන් - SC Guide

Thread States and Synchronization Demystified SC Guide
මචංලා, කොහොමද? අද අපි කතා කරන්න යන්නේ ටිකක් serious, ඒත් software development වලදී නැතුවම බැරි topic එකක් ගැන – ඒ තමයි Thread States සහ Synchronization.
හිතන්නකො ඔයාලා ගෙදරක වැඩ කරද්දි, එක පාරටම Task ගොඩක් එනවා කියලා. උදේට තේ හදන්න, දරුවව ඉස්කෝලේ යවන්න, Office යන්න ලෑස්ති වෙන්න. මේ හැමදේම එකම කෙනෙක් එකින් එකට කළොත් ගොඩක් වෙලා යනවා නේද? එහෙම නැතුව, ගෙදර ඉන්න කට්ටිය අතර මේ වැඩ බෙදාගෙන, එක පාරටම වැඩ ගොඩක් කරගත්තොත් අම්මෝ, කොච්චර වෙලාව ඉතුරුද! මේක හරියට software වල Multi-threading වගේ තමයි.
Software development වලදී, විශේෂයෙන් Java වගේ languages වලදී, අපිට එක program එකක් ඇතුළේ එකවර tasks කීපයක් parallelly run කරන්න පුළුවන්. මේකට තමයි අපි Threads කියන්නේ. ඒත් මේ Threads හැම වෙලාවෙම වැඩ කරන්නේ නැහැ. එයාලට එක එක states තියෙනවා. සමහර වෙලාවට එයාලා බ්ලොක් වෙනවා, සමහර වෙලාවට වැඩ කරනවා, තවත් වෙලාවට අලුතින්ම හැදෙනවා විතරයි. මේ States ගැන නොදැන Threading කරද්දි තමයි වැඩේ අවුල් වෙන්නේ. ඒ වගේම, Threads කිහිපයක් එකම resource එකක් (data එකක් වගේ) පාවිච්චි කරන්න ගියාම, පොඩි අවුලක් වෙනවා. ඒක වළක්වගන්න තමයි Synchronization කියන concept එක ආවේ.
ඉතින්, අද අපි මේ Thread States මොනවද, Synchronization කියන්නේ මොකක්ද, ඒක කොහොමද වැඩ කරන්නේ වගේ දේවල් ඉතාම සරලව, උදාහරණ එක්ක බලමු. මේ දැනුම ඔයාගේ coding skill එක තවත් අලුත් level එකකට ගෙනියන්න අනිවාර්යයෙන්ම උදව් වෙයි.
Thread මොනවද මේ? - නූලක් වගේ වැඩ කරන අය (What are Threads? - Workers Like Threads)
සරලව කිව්වොත්, Thread එකක් කියන්නේ program එකක් ඇතුළේ තියෙන, ස්වාධීනව ක්රියාත්මක වෙන්න පුළුවන්, සැහැල්ලු (lightweight) execution path එකක්. Main program එකත් ඇත්තටම එක Thread එකක්. ඒකට අපි Main Thread එක කියනවා.
හිතන්න ඔයාගේ computer එකේ web browser එකක්, media player එකක්, සහ text editor එකක් එකම වෙලාවේ run වෙනවා කියලා. මේ හැම එකක්ම වෙන වෙනම processes. ඒත් browser එක ඇතුළෙත් එකම වෙලාවෙ tab කිහිපයක් විවෘත වෙලා තියෙන්න පුළුවන්, background එකේ updates download වෙන්න පුළුවන්. මේ tab එකක්, download එකක් වගේ දේවල් වලට Thread එකක් වෙන් වෙන්න පුළුවන්. Process එකකට threads ගොඩක් තියෙන්න පුළුවන්.
ඇයි අපිට Threads ඕන?
- Responsiveness: ඔයාගේ program එකේ UI එක freeze නොවී තියාගන්න. ගොඩක් වෙලා යන operation එකක් background thread එකකට දාලා, UI thread එක free කරන්න පුළුවන්.
- Resource Utilization: Multi-core processors වලදී, Threads කිහිපයක් පාවිච්චි කරලා CPU cores කිහිපයක එකම වෙලාවේ වැඩ කරවන්න පුළුවන්.
- Simplicity: සමහර complex tasks, Threads වලට කඩලා simplify කරන්න පුළුවන්.
හැබැයි මේ Threads පාවිච්චි කරද්දි, එයාලා හැසිරෙන විදිය ගැන හොඳ අවබෝධයක් තියෙන්න ඕන. එතනදි තමයි Thread States වැදගත් වෙන්නේ.
Thread States - නූලක ජීවන චක්රය (The Life Cycle of a Thread)
Java වලදී, Thread එකකට යම්කිසි වෙලාවක තියෙන්න පුළුවන් states ටිකක් තියෙනවා. මේවා java.lang.Thread.State
enum එකෙන් define කරලා තියෙන්නේ. අද අපි ප්රධාන states තුනක් ගැන අවධානය යොමු කරමු:
- NEW:
- නමින්ම කියවෙනවා වගේ, මේ state එකේ ඉන්නේ අලුතින්ම හදපු Thread එකක්. ඒ කියන්නේ
new Thread()
කියලා object එකක් හැදුවට, ඒක තාමstart()
කරලා නැහැ. ඒ Thread එක තාම CPU එකේ run වෙන්න ලෑස්ති නැහැ. හිතන්න, ඔයා අලුත් වැඩකරුවෙක්ව ගෙනාවා විතරයි, තාම වැඩට දාලා නැහැ වගේ දෙයක්. - RUNNABLE:
- Thread එකක්
start()
කළාට පස්සේ, ඒක Runnable state එකට එනවා. මේකෙන් කියවෙන්නේ ඒ Thread එක දැන් CPU එකේ run වෙන්න පුළුවන් තත්වෙක ඉන්නවා කියන එකයි. CPU එකේ scheduler එක තමයි මේ Threads අතරේ CPU time එක බෙදලා දෙන්නේ. ඒ නිසා Runnable state එකේ ඉන්න Thread එකක් ඇත්තටම run වෙනවා වෙන්නත් පුළුවන්, නැත්නම් run වෙන්න waiting list එකේ ඉන්නවා වෙන්නත් පුළුවන්. හරියටම කිව්වොත්, වැඩට දාලා තියෙනවා, දැන් එයාට වැඩ කරන්න පුළුවන්, හැබැයි එයාට තව වැඩේ ලැබිලා නැති වෙන්නත් පුළුවන්. - BLOCKED:
- මෙන්න මෙතන තමයි වැඩේ වැදගත් වෙන්නේ. Thread එකක් Blocked state එකට එන්නේ, ඒ Thread එකට යම්කිසි monitor lock එකක් (monitor lock කියන්නේ shared resource එකක් access කරන්න අවශ්ය වෙන lock එකක්) acquire කරගන්න බැරි වුණාමයි. ඒ කියන්නේ, වෙන Thread එකක් දැනටමත් ඒ resource එක පාවිච්චි කරනවා නම්, මේ Thread එකට ඒක පාවිච්චි කරන්න බැහැ, එයාට එතනම Block වෙලා ඉන්න වෙනවා. හිතන්න, එක bathroom එකක් තියෙන ගෙදරක, කෙනෙක් ඇතුළේ ඉද්දි තව කෙනෙක්ට එළියේ Block වෙලා ඉන්න වෙනවා වගේ.
- Block වෙන්න ප්රධානම හේතුව තමයි
synchronized
block හෝ method එකකට ඇතුළු වෙන්න උත්සාහ කරද්දි, ඒ monitor lock එක වෙන Thread එකක් අල්ලගෙන ඉන්න එක. - මේ ප්රධාන states වලට අමතරව
WAITING
,TIMED_WAITING
,TERMINATED
කියන states ත් තියෙනවා. ඒත් අද අපේ ප්රධාන අවධානය මේ තුනට. - ඔයාලට මතකද, අපි කලින් කිව්වා Threads කිහිපයක් එකම shared resource එකක් (variable එකක්, file එකක්, database connection එකක් වගේ) එකවර access කරන්න ගියොත් ප්රශ්න ඇතිවෙනවා කියලා? මේකට අපි Race Condition කියලා කියනවා. උදාහරණයක් විදියට, Threads දෙකක් එකම account balance එක update කරන්න ගියොත්, වැරදි final balance එකක් ලැබෙන්න පුළුවන්.
- මේ වගේ අවස්ථා වලදී අපිට Synchronization පාවිච්චි කරන්න වෙනවා. Synchronization මගින් වගබලා ගන්නේ, යම්කිසි code block එකකට හෝ method එකකට එකවර ඇතුළු වෙන්න පුළුවන් එක Thread එකකට විතරයි කියන එකයි. ඒක හරියටම traffic light එකක් වගේ. එක පාරටම එක දිශාවක වාහනවලට විතරයි යන්න දෙන්නේ. අනිත් අයට බ්ලොක් වෙලා ඉන්න වෙනවා.
- Java වල Synchronization කරන්න ප්රධානම විදිය තමයි
synchronized
keyword එක පාවිච්චි කරන එක. - Method එකක්
synchronized
කළාම, ඒ method එකට ඇතුළු වෙන්න පුළුවන් එක Thread එකකට විතරයි. අනිත් Threads වලට ඒ method එකේ monitor lock එක නිදහස් වෙනකම් Block වෙලා ඉන්න වෙනවා. මේ lock එක තියෙන්නේ method එක අයිති object එක මතයි. - මේ උදාහරණයේදී,
increment()
method එකsynchronized
නිසා, එක වරකට එක Thread එකකට විතරයිcount
variable එක modify කරන්න පුළුවන්. ඒ නිසා අපිට නිවැරදි final value එක ලැබෙනවා. - සමහර වෙලාවට, අපිට method එකක්ම
synchronized
කරන්න අවශ්ය වෙන්නේ නැහැ. Method එක ඇතුළේ පොඩි කොටසක් විතරක් ආරක්ෂා කරගන්න ඕන නම්, අපිටsynchronized
block එකක් පාවිච්චි කරන්න පුළුවන්. මේකේ වාසිය තමයි, අපිට Lock එකට object එකක් specify කරන්න පුළුවන් වීම. synchronized (dataLock) { ... }
කියන block එක ඇතුළේ තියෙන code එක එකවර run වෙන්න පුළුවන් එක Thread එකකට විතරයි. මේකෙන් performance එක වැඩි කරගන්න පුළුවන්, මොකද මුළු method එකම lock කරනවා වෙනුවට, critical section එක විතරක් lock කරන නිසා.- දැන් ඔයාලට තේරෙනවා ඇති,
synchronized
keyword එක Thread States වලට කොහොමද බලපාන්නේ කියලා. Thread එකක්synchronized
method එකකට හෝ block එකකට ඇතුල් වෙන්න උත්සාහ කරද්දි, ඒ monitor lock එක වෙන Thread එකක් අරගෙන හිටියොත්, අලුතින් එන Thread එක BLOCKED state එකට යනවා. අර කලින් කිව්වා වගේ, bathroom එකේ කෙනෙක් ඉන්නකම් එළියේ බ්ලොක් වෙලා ඉන්නවා වගේ. synchronized
පාවිච්චි කිරීමෙන් Data Consistency එක හොඳටම ආරක්ෂා කරගන්න පුළුවන්. ඒත් ඒක ඕනෑවට වඩා පාවිච්චි කළොත්, Deadlock (Threads දෙකක් එකිනෙකාට අවශ්ය lock එකක් එනකම් අනන්තවත් Block වෙලා ඉන්න තත්වය) වගේ ප්රශ්න ඇතිවෙන්න පුළුවන්, ඒ වගේම program එකේ performance එකත් අඩු වෙන්න පුළුවන්. ඉතින්, කොතනද synchronization ඕන, කොතනද අනවශ්ය කියන එක තේරුම් ගන්න එක ගොඩක්ම වැදගත්.- Threads කියන්නේ modern applications develop කරද්දී නැතුවම බැරි concept එකක්. එයාලාගේ states තේරුම් ගැනීමයි, shared resources ආරක්ෂා කරන්න synchronization කොහොමද පාවිච්චි කරන්නේ කියන එකයි, quality code ලියන්න අත්යවශ්යයි. Multi-threading කියන්නේ පොඩ්ඩක් complex topic එකක් වුණාට, මේ basics ටික තේරුම් ගත්තා නම් ඔයාලට ඕනම complex scenario එකක් handle කරන්න පුළුවන්. හැම වෙලාවෙම මතක තියාගන්න, Threads ගොඩක් අතරේ coordination නැති වුණොත් එන්නේ අවුල් විතරයි!
- ඉතින්, මේ article එකෙන් ඔයාලට Thread States සහ Synchronization ගැන හොඳ අවබෝධයක් ලැබෙන්න ඇති කියලා හිතනවා. ඔයාලට මේ සම්බන්ධයෙන් මොනවා හරි ප්රශ්න තියෙනවා නම්, හරි ඔයාලාගේ අත්දැකීම් මොනවාද කියලා දැනගන්න කැමති නම්, අනිවාර්යයෙන්ම පහලින් comment එකක් දාගෙන යන්න. ඒ වගේම මේ concepts ඔයාගේ next project එකේදී implement කරලා බලන්නත් අමතක කරන්න එපා. හරි එහෙනම්, තවත් අලුත් Technical Article එකකින් හමුවෙමු!
නිගමනය (Conclusion)
Thread States & Synchronization එකට (Thread States and Synchronization Together)
class DataProcessor {
private int data = 0;
private final Object dataLock = new Object(); // Custom lock object
public void processData() {
// Unsynchronized operations can happen here
System.out.println(Thread.currentThread().getName() + ": Doing some non-critical work.");
synchronized (dataLock) { // Synchronized block using a custom lock
data++; // This is the critical section
System.out.println(Thread.currentThread().getName() + ": Data updated to " + data);
}
// More unsynchronized operations
System.out.println(Thread.currentThread().getName() + ": Finished critical work.");
}
public int getData() {
return data;
}
}
public class SynchronizedBlockExample {
public static void main(String[] args) throws InterruptedException {
DataProcessor processor = new DataProcessor();
Runnable task = () -> {
for (int i = 0; i < 500; i++) {
processor.processData();
}
};
Thread t1 = new Thread(task, "Worker-1");
Thread t2 = new Thread(task, "Worker-2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final data: " + processor.getData());
// Expected Output: Final data: 1000
}
}
synchronized
Blocks
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " incremented to: " + count);
}
public int getCount() {
return count;
}
}
public class SynchronizedMethodExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread t1 = new Thread(task, "Thread-1");
Thread t2 = new Thread(task, "Thread-2");
t1.start();
t2.start();
t1.join(); // Wait for t1 to finish
t2.join(); // Wait for t2 to finish
System.out.println("Final count: " + counter.getCount());
// Expected Output: Final count: 2000 (without synchronization it could be less due to race condition)
}
}
synchronized
Methods
Synchronization - පොදු දේවල් ආරක්ෂා කරගමු (Let's Protect Shared Things)
public class BlockedExample {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("T1 acquired lock.");
try {
Thread.sleep(2000); // T1 holds the lock for 2 seconds
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("T1 released lock.");
}
});
Thread t2 = new Thread(() -> {
System.out.println("T2 trying to acquire lock.");
synchronized (lock) { // T2 will be blocked here if T1 holds the lock
System.out.println("T2 acquired lock.");
}
});
t1.start();
Thread.sleep(500); // Give T1 a chance to acquire the lock
t2.start();
Thread.sleep(100); // Give T2 a chance to attempt acquiring the lock
System.out.println("State of T2: " + t2.getState());
// Expected Output: State of T2: BLOCKED
}
}
Thread myRunnableThread = new Thread(() -> {
try {
Thread.sleep(100); // Simulate some work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("I'm runnable and running!");
});
myRunnableThread.start();
System.out.println("State after start: " + myRunnableThread.getState());
// Output: State after start: RUNNABLE (Most likely)
Thread myNewThread = new Thread(() -> {
System.out.println("I'm a new thread!");
});
System.out.println("State after creation: " + myNewThread.getState());
// Output: State after creation: NEW