⚙️
Multithreading

Advanced Multithreading

Thread Lifecycle & Executors
💡 Thread lifecycle एक runner के सफ़र जैसा है — New (start line पर खड़ा) -> Runnable (दौड़ने को तैयार) -> Running (दौड़ रहा) -> Waiting (रुककर दम ले रहा) -> Terminated (finish line)। Executor Framework एक race manager है जो automatically decide करता है कौनसा runner कब दौड़े।

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 एक runner के सफ़र जैसा है — New (start line पर खड़ा) -> Runnable (दौड़ने को तैयार) -> Running (दौड़ रहा) -> Waiting (रुककर दम ले रहा) -> Terminated (finish line)। Executor Framework एक race manager है जो automatically decide करता है कौनसा runner कब दौड़े।
1 / 7
⚡ झट से Recap
  • Thread lifecycle: New->Runnable->Running->Waiting->Terminated
  • ExecutorService = thread pool manager
  • Deadlock = threads हमेशा के लिए अटक जाते हैं
इस page में (6 subtopics)

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
  }
}
💡Tip: सिर्फ एक simple counter के लिए synchronized की जगह java.util.concurrent.atomic.AtomicInteger use करना अक्सर बेहतर और faster होता है — incrementAndGet() जैसी methods lock-free तरीके से thread-safe होती हैं।

ये तीनों 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मतलब
NewThread object बना, लेकिन start() नहीं हुआ
Runnablestart() call हो गया, CPU मिलने का wait/चल रहा है
Runningअभी CPU पर actually चल रहा है
Waiting/Blockedwait(), sleep(), या lock का इंतज़ार
Terminatedrun() 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 honge
💡Tip: हमेशा pool.shutdown() call करो जब काम खत्म हो — वरना threads चलते रह सकते हैं और program exit ही नहीं होगा (या resources leak होंगे)।

Deadlock तब होता है जब 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 कर देता है।

⚠️Common Mistake: Deadlock detect करना production में बहुत मुश्किल है (program बस "hang" हो जाता है, कोई error नहीं आती)। Lock ordering consistency maintain करना सबसे simple और reliable prevention है।

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 चाहिए।