A thread is a lightweight process, the smallest unit of processing that can be scheduled by an operating system. Java supports multithreading through the java.lang.Thread class.
Program vs Process vs Thread:
Program: Static code (set of instructions)
Process: Running instance of a program with its own memory space
Thread: Lightweight sub-process within a process, shares memory
A race condition occurs when multiple threads access shared data concurrently, leading to unpredictable results.
public class Counter { private int count = 0; // UNSAFE - Race condition public void increment() { count++; // Not atomic: read-modify-write } public int getCount() { return count; }}// Problem demonstrationpublic class RaceConditionDemo { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Count: " + counter.getCount()); // Expected: 2000, Actual: Often less due to race condition }}
public class SynchronizedCounter { private int count = 0; // SAFE - synchronized method public synchronized void increment() { count++; } public synchronized int getCount() { return count; }}
Synchronized blocks provide finer-grained control over synchronization, improving performance by locking only critical sections.
public class SynchronizedBlockExample { private int count = 0; private Object lock = new Object(); public void increment() { synchronized (lock) { count++; } } // Synchronized on this object public void decrement() { synchronized (this) { count--; } }}
import java.util.concurrent.*;public class CallableExample { public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); // Callable returns a result Callable<Integer> task = () -> { Thread.sleep(2000); return 42; }; // Submit callable and get Future Future<Integer> future = executor.submit(task); System.out.println("Task submitted"); // Do other work... // Get result (blocks if not ready) Integer result = future.get(); System.out.println("Result: " + result); executor.shutdown(); }}
import java.util.concurrent.ConcurrentHashMap;ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();// Thread-safe operationsmap.put("key1", 1);map.putIfAbsent("key2", 2);map.computeIfAbsent("key3", k -> 3);// Atomic operationsmap.compute("key1", (k, v) -> v == null ? 1 : v + 1);
import java.util.concurrent.*;// Producer-Consumer patternBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);// Producernew Thread(() -> { try { for (int i = 0; i < 20; i++) { queue.put(i); // Blocks if full System.out.println("Produced: " + i); } } catch (InterruptedException e) { e.printStackTrace(); }}).start();// Consumernew Thread(() -> { try { while (true) { Integer item = queue.take(); // Blocks if empty System.out.println("Consumed: " + item); } } catch (InterruptedException e) { e.printStackTrace(); }}).start();
import java.util.concurrent.CopyOnWriteArrayList;// Thread-safe list optimized for readsCopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();list.add("Item 1");list.add("Item 2");// Safe iteration even during modificationfor (String item : list) { System.out.println(item);}
public class EagerSingleton { private static final EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton() {} public static EagerSingleton getInstance() { return INSTANCE; }}