Advanced Multithreading
Thread के 5 states होते हैं: New, Runnable, Running, Waiting/Blocked, Terminated।
wait()/notify() threads को एक दूसरे के signal का इंतज़ार करने देते हैं। ExecutorService thread pool manage करता है — नए threads manually बनाने के बजाय, ready threads reuse होते हैं।
Deadlock तब होता है जब दो threads एक-दूसरे के पास जो resource है उसका इंतज़ार करते रह जाते हैं, और कोई आगे नहीं बढ़ता — जैसे दो लोग एक ही दरवाज़े पर एक-दूसरे को पहले जाने दे रहे हों, हमेशा के लिए।
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> System.out.println("Task running!"));
pool.shutdown();- Thread lifecycle: New->Runnable->Running->Waiting->Terminated
- ExecutorService = thread pool manager
- Deadlock = threads हमेशा के लिए अटक जाते हैं
synchronized method (पूरा method lock हो जाता है) simple है लेकिन पूरे method को lock करता है, चाहे सिर्फ एक छोटा हिस्सा critical हो — इससे unnecessary blocking हो सकती है (दूसरे threads जो unrelated काम करना चाहते हैं, वो भी wait करेंगे)।
synchronized block (synchronized(obj) { ... }) सिर्फ ज़रूरी हिस्से को lock करता है — better performance, क्योंकि बाकी code parallel चल सकता है। हर object का अपना "monitor lock" होता है जो synchronized use करता है।
synchronized void increment() { count++; } // poora method locked
void increment2() {
// ... non-critical code, parallel chal sakta hai
synchronized(this) {
count++; // sirf ye critical section locked
}
}ये तीनों Object class के methods हैं (हर object पर available), और सिर्फ synchronized block के अंदर ही use हो सकते हैं (वरना IllegalMonitorStateException आएगी)। wait() current thread को "सुला" देता है (और lock छोड़ देता है) जब तक कोई दूसरा thread notify() न करे।
Classic use-case: Producer-Consumer pattern, जहाँ consumer wait() करे जब तक producer कुछ produce न करे और notify() न करे। notifyAll() सब waiting threads को जगाता है (सिर्फ एक नहीं), जो safer होता है जब multiple threads wait कर रहे हों।
हर thread अपनी "life" में 5 states से गुज़रता है, New से शुरू होके Terminated तक। यह समझना ज़रूरी है debugging के लिए — जैसे अगर तुम्हारा thread "stuck" लग रहा है, उसकी state check करके पता चल सकता है वो Waiting में है या Blocked में।
| State | मतलब |
|---|---|
| New | Thread object बना, लेकिन start() नहीं हुआ |
| Runnable | start() call हो गया, CPU मिलने का wait/चल रहा है |
| Running | अभी CPU पर actually चल रहा है |
| Waiting/Blocked | wait(), sleep(), या lock का इंतज़ार |
| Terminated | run() complete हो गया, thread खत्म |
Manually thread बनाते रहना expensive है (हर thread बनाना OS resources लेता है)। ExecutorService thread pool manage करता है: newFixedThreadPool(n) — fixed number of reusable threads, जैसे n workers हमेशा ready। newCachedThreadPool() — ज़रूरत के हिसाब से बढ़ता-घटता pool, काम खत्म होते ही idle threads free हो जाते हैं। newScheduledThreadPool() — delay या repeat interval पर tasks चलाने के लिए।
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> doTask());
pool.shutdown(); // naye tasks accept karna band, purane complete hongeDeadlock तब होता है जब Thread A, Lock-1 पकड़ के Lock-2 का wait करे, और Thread B, Lock-2 पकड़ के Lock-1 का wait करे — दोनों हमेशा के लिए रुक जाते हैं, क्योंकि दोनों एक-दूसरे के पास जो है उसका इंतज़ार कर रहे हैं।
Prevention: हमेशा locks को *same order* में acquire करो सारे threads में (जैसे हमेशा पहले Lock-1, फिर Lock-2, कभी उल्टा नहीं) — इससे "circular wait" ही possible नहीं होता। या timeout वाले locks (tryLock(timeout)) use करो, जिससे thread हमेशा के लिए अटकता नहीं, timeout के बाद give up कर देता है।
Multi-threaded environment में हर thread की अपनी CPU cache हो सकती है एक variable की "local copy" (performance के लिए) — इसलिए एक thread का change तुरंत दूसरे thread को न दिखे, यह possible है।
volatile keyword guarantee देता है कि variable की reads/writes हमेशा main memory से हों, cache से नहीं — इससे एक thread का change तुरंत दूसरे threads को दिखता है। यह synchronization जैसा mutual-exclusion (एक वक्त सिर्फ एक thread) नहीं देता, सिर्फ "visibility" guarantee देता है — अगर count++ जैसी compound operation है, volatile काफी नहीं है, synchronized/Atomic चाहिए।