Java Advanced

πŸ—οΈ OOP Internals & Runtime Mechanics

Object-Oriented Programming in Java is deeply integrated with JVM execution. Understanding how Java implements OOP at the bytecode and runtime level is essential for performance optimization and debugging complex applications.

🎯 Virtual Method Tables (Vtables)

Each class has a vtable containing pointers to its methods. When a method is called via invokevirtual, the JVM:

  1. Retrieves the object's vtable pointer from the object header
  2. Looks up the method slot at a fixed offset
  3. Invokes the method through the pointer

This indirection enables polymorphism but adds a small overhead. The JIT compiler aggressively optimizes this through method inlining and devirtualization.

πŸ”§ Bytecode Instructions

  • invokevirtual: Instance method dispatch (polymorphic)
  • invokespecial: Constructor, private, and super calls
  • invokestatic: Static method calls
  • invokeinterface: Interface method calls (uses itable)
  • invokedynamic: Dynamic method invocation (lambdas, invokedynamic)

πŸ“¦ Object Layout in Memory

Every Java object has a header containing:

  • Mark Word: Hash code, GC age, lock state
  • Klass Pointer: Pointer to class metadata (vtable location)
  • Instance Fields: Object's instance variables
  • Padding: Alignment to 8-byte boundaries

⚑ Performance Optimizations

  • Method Inlining: JIT replaces method calls with method body for small methods
  • Escape Analysis: Stack allocation for objects that don't escape method
  • Lock Elision: Removes unnecessary synchronization
  • Devirtualization: Converts virtual calls to direct calls when type is known
πŸ“Œ Vtable Dispatch πŸ“Œ Bytecode Instructions πŸ“Œ Object Header πŸ“Œ JIT Optimizations

Understanding Method Dispatch Performance

Static methods (invokestatic) are the fastest - resolved at compile time. Final methods can be devirtualized, making them nearly as fast as static calls. Interface calls (invokeinterface) are slightly slower due to itable lookup overhead. The JIT's inline caching remembers the last type seen, making repeated calls to the same type extremely fast.

// OOP Internals Deep Dive class Animal { // Final method - can be inlined by JIT public final String getSpecies() { return "Animal"; } // Virtual method - polymorphic dispatch public void speak() { System.out.println("Some sound"); } // Static method - resolved at compile time public static void info() { System.out.println("Animal class"); } } class Dog extends Animal { @Override public void speak() { System.out.println("Woof! Woof!"); } // New method public void fetch() { System.out.println("Fetching ball"); } } class Cat extends Animal { @Override public void speak() { System.out.println("Meow!"); } } public class OOPInternalsDemo { public static void main(String[] args) { // Polymorphic array Animal[] animals = { new Dog(), new Cat(), new Dog() }; // Virtual dispatch - method resolved at runtime for (Animal a : animals) { a.speak(); // invokevirtual - polymorphic } // Static dispatch - compile-time Animal.info(); // invokestatic // Final method - can be devirtualized for (Animal a : animals) { System.out.println(a.getSpecies()); // invokevirtual but final } // Type checking and casting for (Animal a : animals) { if (a instanceof Dog) { Dog d = (Dog) a; // Checkcast instruction d.fetch(); } } } }

πŸ“š Collections Framework Deep Dive

Java Collections Framework provides high-performance data structures with well-defined contracts. Understanding internal implementations is crucial for choosing the right collection for your use case.

πŸ—ΊοΈ HashMap Implementation (Java 8+)

HashMap uses array of Node<K,V> buckets with linked list chaining. When bucket size exceeds 8, it converts to Red-Black Tree (O(log n) worst case).

  • Initial Capacity: 16 (default), load factor: 0.75
  • Resizing: Doubles capacity when threshold exceeded
  • Hash Function: (key.hashCode()) ^ (key.hashCode() >>> 16)
  • Collision Resolution: Chaining with linked lists, then trees

🌳 TreeMap Implementation

Red-Black tree implementation providing sorted order and O(log n) operations. Implements NavigableMap for nearest-key queries.

πŸ“Š ArrayList vs LinkedList Performance

OperationArrayListLinkedList
get(int index)O(1)O(n)
add(E element)O(1) amortizedO(1)
add(int index, E)O(n)O(n)
remove(int index)O(n)O(n)
Memory overheadLowHigh (extra pointers)

⚑ ConcurrentHashMap Internals

Java 8+ uses CAS (Compare-And-Swap) for lock-free operations and synchronized for bucket-level locking. No full map locking, enabling high concurrency.

🎯 Best Practices

  • Initialize HashMap with expected capacity to avoid resizing
  • Override equals() and hashCode() consistently
  • Use Collections.unmodifiable*() for immutable views
  • Consider EnumMap for enum keys (extremely fast)
  • Use LinkedHashMap for LRU cache implementations
πŸ“Œ HashMap Treeification πŸ“Œ Load Factor πŸ“Œ ConcurrentHashMap CAS πŸ“Œ Fail-Fast Iterators

HashMap Resizing Mechanics

When HashMap resizes, it rehashes all entries into a new array of double the size. This is O(n) operation and can cause latency spikes. Setting initialCapacity to (expectedSize / 0.75f) + 1 prevents resizing. Java 8's treeification ensures worst-case performance stays O(log n) even under hash collisions.

// Collections Framework Deep Dive import java.util.*; import java.util.concurrent.*; public class CollectionsDeepDive { public static void main(String[] args) { // HashMap with capacity planning int expectedSize = 1000; int capacity = (int) (expectedSize / 0.75f) + 1; Map map = new HashMap<>(capacity); // Custom equals/hashCode example class Person { String name; int id; Person(String name, int id) { this.name = name; this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person p = (Person) o; return id == p.id; } @Override public int hashCode() { return Integer.hashCode(id); } @Override public String toString() { return name + "(" + id + ")"; } } Set personSet = new HashSet<>(); personSet.add(new Person("Alice", 1)); personSet.add(new Person("Bob", 2)); personSet.add(new Person("Alice", 1)); // Duplicate, won't add System.out.println("Person Set: " + personSet); // ConcurrentHashMap with compute operations ConcurrentHashMap chm = new ConcurrentHashMap<>(); chm.put("A", 1); // Atomic update chm.compute("A", (k, v) -> v == null ? 1 : v + 1); chm.merge("B", 1, Integer::sum); System.out.println("ConcurrentHashMap: " + chm); // LinkedHashMap as LRU cache class LRUCache extends LinkedHashMap { private final int maxSize; LRUCache(int maxSize) { super(16, 0.75f, true); // accessOrder=true this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > maxSize; } } LRUCache cache = new LRUCache<>(3); cache.put(1, "One"); cache.put(2, "Two"); cache.put(3, "Three"); cache.get(1); // Access makes it most recent cache.put(4, "Four"); // Removes eldest (key 2) System.out.println("LRU Cache: " + cache); // PriorityQueue (Min-Heap) PriorityQueue pq = new PriorityQueue<>(); pq.offer(5); pq.offer(1); pq.offer(3); pq.offer(2); System.out.print("PriorityQueue order: "); while (!pq.isEmpty()) { System.out.print(pq.poll() + " "); } System.out.println(); // Collections utilities List numbers = Arrays.asList(3, 1, 4, 1, 5, 9); System.out.println("Max: " + Collections.max(numbers)); System.out.println("Min: " + Collections.min(numbers)); System.out.println("Frequency of 1: " + Collections.frequency(numbers, 1)); // Synchronized wrapper List syncList = Collections.synchronizedList(new ArrayList<>()); syncList.add("Thread-safe"); } }

🧠 JVM Memory Model & Happens-Before

The Java Memory Model (JMM) defines how threads interact through memory, establishing visibility and ordering guarantees essential for correct concurrent programming.

πŸ“Š Memory Areas

  • Heap (Shared): Objects and arrays, garbage collected
  • Stack (Per-Thread): Local variables, method frames, partial results
  • Metaspace (Shared): Class metadata, method bytecode, static variables
  • PC Register (Per-Thread): Current instruction pointer
  • Native Method Stack: JNI native methods
  • Code Cache: JIT-compiled native code

⚑ Happens-Before Rules

If A happens-before B, then all memory writes in A are visible to B:

  • Program Order: Actions in a thread happen in program order
  • Monitor Lock: unlock() happens-before subsequent lock()
  • Volatile Variable: write happens-before subsequent read
  • Thread Start: start() happens-before thread's first action
  • Thread Termination: Last action in thread happens-before join()
  • Transitivity: If A hb B and B hb C, then A hb C

πŸ”’ Synchronization Mechanisms

  • synchronized: Mutual exclusion + visibility (monitor lock)
  • volatile: Visibility guarantee, prevents reordering
  • final: Safe publication (if properly constructed)
  • Atomic classes: CAS-based lock-free operations

🚫 Common Issues

  • Race Condition: Outcome depends on thread timing
  • Data Race: Concurrent unsynchronized access to shared variable
  • Memory Consistency Error: Thread sees stale data
  • Instruction Reordering: Compiler/CPU may reorder instructions
πŸ“Œ Heap vs Stack πŸ“Œ Happens-Before πŸ“Œ Volatile Guarantees πŸ“Œ Memory Barriers

Understanding Volatile

Volatile provides visibility guarantees but not atomicity. Writing to a volatile flushes the value to main memory; reading from volatile reloads from main memory. It also prevents instruction reordering by inserting memory barriers (StoreStore, StoreLoad). Use volatile for simple flags, counters with single-thread writes, or when you need visibility without mutual exclusion.

// JVM Memory Model & Happens-Before public class MemoryModelDemo { // Without volatile, this may never become visible private volatile boolean running = true; private int counter = 0; // Race condition without synchronization public void unsafeIncrement() { counter++; // Not atomic: read-modify-write } // Safe increment with synchronization public synchronized void safeIncrement() { counter++; } public static void main(String[] args) throws InterruptedException { MemoryModelDemo demo = new MemoryModelDemo(); // Visibility demonstration Thread writer = new Thread(() -> { int local = 0; while (demo.running) { demo.unsafeIncrement(); local++; if (local % 10000 == 0) { // Optional: yield to allow reader Thread.yield(); } } System.out.println("Writer stopped at: " + local); }); Thread reader = new Thread(() -> { try { Thread.sleep(100); int lastSeen = -1; for (int i = 0; i < 5; i++) { int current = demo.counter; if (current != lastSeen) { System.out.println("Reader sees: " + current); lastSeen = current; } Thread.sleep(50); } demo.running = false; // Volatile write - visible to writer } catch (InterruptedException e) {} }); writer.start(); reader.start(); writer.join(); reader.join(); System.out.println("Final counter: " + demo.counter); // Instruction reordering demonstration (conceptual) class ReorderingDemo { int a = 0; boolean flag = false; public void writer() { a = 1; // Write 1 flag = true; // Write 2 - could be reordered! } public void reader() { if (flag) { // Could see a == 0 if reordering occurred System.out.println("a = " + a); } } } // Fix with volatile class FixedDemo { int a = 0; volatile boolean flag = false; // Prevents reordering public void writer() { a = 1; // StoreStore barrier prevents reordering flag = true; } public void reader() { if (flag) { // LoadLoad barrier ensures a is visible System.out.println("a = " + a); } } } System.out.println("\nMemory Model Demo Complete"); } }

πŸ—‘οΈ Garbage Collection Deep Dive

Garbage Collection automatically manages heap memory, reclaiming objects no longer referenced. Understanding GC algorithms is crucial for tuning application performance.

♻️ Generational Hypothesis

Most objects die young. This observation drives generational GC design:

  • Young Generation: Eden, Survivor S0, Survivor S1
  • Old Generation (Tenured): Long-lived objects
  • Metaspace: Class metadata (Java 8+)

πŸ“Š GC Algorithms

  • Serial GC: Single-threaded, mark-compact (client apps)
  • Parallel GC (Throughput): Multi-threaded young gen collection
  • CMS (Concurrent Mark Sweep): Low pause, concurrent old gen (deprecated)
  • G1 (Garbage First): Region-based, predictable pauses (default)
  • ZGC: Scalable low-latency (sub-millisecond pauses)
  • Shenandoah: Ultra-low pause times

🎯 GC Roots

Objects reachable from roots are considered live:

  • Active threads (stack frames)
  • Static variables
  • JNI global references
  • Monitors (synchronized)
  • JVMTI references

πŸ“ˆ Tuning Parameters

  • -Xms & -Xmx: Initial and maximum heap size
  • -XX:NewRatio: Old/Young generation ratio
  • -XX:SurvivorRatio: Eden/Survivor ratio
  • -XX:+UseG1GC: Enable G1 garbage collector
  • -XX:MaxGCPauseMillis: Target pause time
  • -XX:+PrintGCDetails: Enable GC logging
πŸ“Œ Generational GC πŸ“Œ Stop-The-World πŸ“Œ GC Roots πŸ“Œ Memory Leaks

G1 GC: How It Works

G1 divides heap into regions (1-32MB each). It tracks live data in each region and prioritizes regions with the most garbage (garbage-first). Young collections move objects from Eden to Survivor regions; mixed collections collect young and some old regions. G1 uses SATB (Snapshot-At-The-Beginning) for concurrent marking, ensuring correctness with low pause times.

// Garbage Collection Deep Dive import java.lang.ref.*; import java.util.*; public class GCDemo { private static class LargeObject { private byte[] data = new byte[1024 * 1024]; // 1MB private int id; LargeObject(int id) { this.id = id; } @Override protected void finalize() throws Throwable { System.out.println("Finalizing object " + id); super.finalize(); } } public static void main(String[] args) throws InterruptedException { System.out.println("=== GC Deep Dive Demo ==="); // 1. Reference Types System.out.println("\n1. Reference Types:"); LargeObject strongRef = new LargeObject(1); SoftReference softRef = new SoftReference<>(new LargeObject(2)); WeakReference weakRef = new WeakReference<>(new LargeObject(3)); ReferenceQueue queue = new ReferenceQueue<>(); PhantomReference phantomRef = new PhantomReference<>(new LargeObject(4), queue); System.out.println("Soft ref before GC: " + softRef.get()); System.out.println("Weak ref before GC: " + weakRef.get()); System.gc(); Thread.sleep(500); System.out.println("Soft ref after GC: " + softRef.get()); // May still exist System.out.println("Weak ref after GC: " + weakRef.get()); // Likely null // 2. Memory Leak Simulation System.out.println("\n2. Memory Leak Prevention:"); List cache = new ArrayList<>(); // Bad: Strong references prevent GC for (int i = 0; i < 10; i++) { cache.add(new LargeObject(i)); } // Good: Use WeakHashMap for cache WeakHashMap weakCache = new WeakHashMap<>(); String key = new String("key"); weakCache.put(key, new LargeObject(100)); key = null; // Key becomes eligible for GC System.gc(); Thread.sleep(500); System.out.println("WeakHashMap size after GC: " + weakCache.size()); // 3. GC Tuning Parameters (conceptual) System.out.println("\n3. GC Tuning Tips:"); System.out.println(" -Xms2g -Xmx2g : Set heap size to 2GB"); System.out.println(" -XX:+UseG1GC : Use G1 garbage collector"); System.out.println(" -XX:MaxGCPauseMillis=200 : Target 200ms pauses"); System.out.println(" -XX:+PrintGCDetails : Log GC activity"); System.out.println(" -XX:+HeapDumpOnOutOfMemoryError : Dump heap on OOM"); // 4. JVM Memory Info System.out.println("\n4. Current JVM Memory:"); Runtime rt = Runtime.getRuntime(); System.out.println(" Total Memory: " + rt.totalMemory() / 1024 / 1024 + " MB"); System.out.println(" Free Memory: " + rt.freeMemory() / 1024 / 1024 + " MB"); System.out.println(" Max Memory: " + rt.maxMemory() / 1024 / 1024 + " MB"); System.out.println("\n=== GC Demo Complete ==="); } }

🧡 Multithreading & Thread Lifecycle

Java provides built-in thread support for concurrent programming. Understanding thread lifecycle and synchronization is essential for building responsive applications.

πŸ”„ Thread Lifecycle States

  • NEW: Created but not started
  • RUNNABLE: Ready to run or currently executing
  • BLOCKED: Waiting for monitor lock
  • WAITING: Waiting indefinitely (wait, join, park)
  • TIMED_WAITING: Waiting with timeout
  • TERMINATED: Completed execution

βš™οΈ Thread Creation Methods

  • Extend Thread: Simple but less flexible
  • Implement Runnable: Preferred (can extend other classes)
  • Implement Callable: Returns value, throws exceptions
  • Lambda Expression: Concise syntax (Java 8+)
  • ExecutorService: Managed thread pools

πŸ”’ Synchronization Techniques

  • synchronized methods: Lock on instance or class
  • synchronized blocks: More granular control
  • ReentrantLock: Explicit locking with conditions
  • ReadWriteLock: Multiple readers, single writer
  • Atomic classes: Lock-free thread-safe operations

🚨 Common Problems

  • Deadlock: Circular wait for resources
  • Livelock: Threads keep changing state
  • Starvation: Thread never gets CPU time
  • Race Condition: Outcome depends on timing
πŸ“Œ Thread States πŸ“Œ Synchronization πŸ“Œ Deadlock Prevention πŸ“Œ Thread Pools

Deadlock Prevention Strategies

To prevent deadlocks: Lock ordering (always acquire locks in same order), timeout (tryLock with timeout), lock hierarchy (acquire higher-level locks before lower-level), and avoid nested locks when possible. Use jstack to detect deadlocks in running applications.

// Multithreading Deep Dive import java.util.concurrent.*; import java.util.concurrent.locks.*; public class MultithreadingDemo { private static int counter = 0; private static final Object monitor = new Object(); private static final ReentrantLock lock = new ReentrantLock(); // Race condition example static class CounterTask implements Runnable { @Override public void run() { for (int i = 0; i < 10000; i++) { // Unsafe increment - race condition counter++; } } } // Safe with synchronized block static class SafeCounterTask implements Runnable { @Override public void run() { for (int i = 0; i < 10000; i++) { synchronized (monitor) { counter++; } } } } // Safe with ReentrantLock static class LockCounterTask implements Runnable { @Override public void run() { for (int i = 0; i < 10000; i++) { lock.lock(); try { counter++; } finally { lock.unlock(); } } } } // Deadlock example static class DeadlockRisk { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void method1() { synchronized (lock1) { sleep(100); synchronized (lock2) { System.out.println("method1 acquired both locks"); } } } public void method2() { synchronized (lock2) { sleep(100); synchronized (lock1) { System.out.println("method2 acquired both locks"); } } } private void sleep(int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) {} } } public static void main(String[] args) throws Exception { System.out.println("=== Multithreading Demo ===\n"); // Race condition demonstration System.out.println("1. Race Condition (unsafe):"); counter = 0; Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(new CounterTask()); threads[i].start(); } for (Thread t : threads) t.join(); System.out.println(" Expected: 100000, Actual: " + counter); // Safe with synchronized System.out.println("\n2. Safe with synchronized:"); counter = 0; for (int i = 0; i < 10; i++) { threads[i] = new Thread(new SafeCounterTask()); threads[i].start(); } for (Thread t : threads) t.join(); System.out.println(" Expected: 100000, Actual: " + counter); // Thread states demonstration System.out.println("\n3. Thread States:"); Thread t = new Thread(() -> { System.out.println(" Thread running..."); try { Thread.sleep(500); } catch (InterruptedException e) {} }); System.out.println(" State after creation: " + t.getState()); t.start(); System.out.println(" State after start: " + t.getState()); Thread.sleep(100); System.out.println(" State during sleep: " + t.getState()); t.join(); System.out.println(" State after completion: " + t.getState()); // Thread pool with ExecutorService System.out.println("\n4. Thread Pool with ExecutorService:"); ExecutorService executor = Executors.newFixedThreadPool(4); for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println(" Task " + taskId + " by " + Thread.currentThread().getName()); try { Thread.sleep(200); } catch (InterruptedException e) {} }); } executor.shutdown(); executor.awaitTermination(3, TimeUnit.SECONDS); // ThreadLocal example System.out.println("\n5. ThreadLocal Demo:"); ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 0); Runnable task = () -> { int value = threadLocal.get(); threadLocal.set(value + 1); System.out.println(" " + Thread.currentThread().getName() + ": " + threadLocal.get()); }; Thread t1 = new Thread(task, "Thread-1"); Thread t2 = new Thread(task, "Thread-2"); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("\n=== Multithreading Demo Complete ==="); } }

⚑ Concurrency Utilities (java.util.concurrent)

The java.util.concurrent package provides high-level concurrency constructs that simplify building thread-safe, scalable applications.

πŸ“¦ Executor Framework

  • ExecutorService: Thread pool management
  • ScheduledExecutorService: Delayed/periodic execution
  • ForkJoinPool: Work-stealing for parallel tasks
  • CompletableFuture: Asynchronous programming

πŸ”§ Synchronization Utilities

  • CountDownLatch: Wait for set of operations
  • CyclicBarrier: Wait for threads to reach barrier
  • Semaphore: Control resource access
  • Exchanger: Exchange data between threads
  • Phaser: Reusable synchronization barrier

⚑ Concurrent Collections

  • ConcurrentHashMap: Thread-safe hash map
  • CopyOnWriteArrayList: Read-heavy thread-safe list
  • BlockingQueue: Thread-safe queue with blocking operations
  • ConcurrentLinkedQueue: Non-blocking thread-safe queue

🎯 Atomic Operations

  • AtomicInteger, AtomicLong: CAS-based counters
  • AtomicReference: Atomic object updates
  • LongAdder: High-performance counter
  • AtomicStampedReference: With version stamp
πŸ“Œ ForkJoinPool πŸ“Œ CompletableFuture πŸ“Œ ConcurrentHashMap πŸ“Œ CAS Operations

CompletableFuture: Async Programming

CompletableFuture enables functional composition of async operations. Methods like thenApply, thenCompose, thenCombine create processing pipelines. allOf and anyOf combine multiple futures. Use supplyAsync for async computation, exceptionally for error recovery, and orTimeout for timeout handling.

// Concurrency Utilities Deep Dive import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.stream.*; public class ConcurrencyUtilitiesDemo { public static void main(String[] args) throws Exception { System.out.println("=== Concurrency Utilities Demo ===\n"); // 1. CountDownLatch System.out.println("1. CountDownLatch:"); CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(() -> { try { Thread.sleep((long)(Math.random() * 1000)); System.out.println(" Task completed: " + Thread.currentThread().getName()); } catch (InterruptedException e) {} finally { latch.countDown(); } }).start(); } latch.await(); System.out.println(" All tasks completed!\n"); // 2. CyclicBarrier System.out.println("2. CyclicBarrier:"); CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println(" All threads reached barrier!")); for (int i = 0; i < 3; i++) { new Thread(() -> { try { System.out.println(" " + Thread.currentThread().getName() + " waiting at barrier"); barrier.await(); System.out.println(" " + Thread.currentThread().getName() + " passed barrier"); } catch (Exception e) {} }).start(); } Thread.sleep(1500); // 3. Semaphore System.out.println("\n3. Semaphore (pool of 2):"); Semaphore semaphore = new Semaphore(2); for (int i = 0; i < 5; i++) { final int id = i; new Thread(() -> { try { semaphore.acquire(); System.out.println(" Thread " + id + " acquired permit"); Thread.sleep(500); System.out.println(" Thread " + id + " releasing"); semaphore.release(); } catch (InterruptedException e) {} }).start(); } Thread.sleep(3000); // 4. CompletableFuture System.out.println("\n4. CompletableFuture:"); CompletableFuture future1 = CompletableFuture.supplyAsync(() -> { sleep(500); return "Hello"; }); CompletableFuture future2 = CompletableFuture.supplyAsync(() -> { sleep(300); return "World"; }); CompletableFuture combined = future1 .thenCombine(future2, (s1, s2) -> s1 + " " + s2 + "!") .thenApply(String::toUpperCase); System.out.println(" Combined: " + combined.get()); // 5. AtomicInteger vs LongAdder System.out.println("\n5. AtomicInteger vs LongAdder:"); AtomicInteger atomicInt = new AtomicInteger(0); LongAdder longAdder = new LongAdder(); // Benchmark AtomicInteger long start = System.nanoTime(); IntStream.range(0, 1000000).parallel().forEach(i -> atomicInt.incrementAndGet()); long atomicTime = System.nanoTime() - start; // Benchmark LongAdder start = System.nanoTime(); IntStream.range(0, 1000000).parallel().forEach(i -> longAdder.increment()); long adderTime = System.nanoTime() - start; System.out.println(" AtomicInteger: " + atomicTime / 1_000_000 + " ms"); System.out.println(" LongAdder: " + adderTime / 1_000_000 + " ms"); System.out.println(" Results: Atomic=" + atomicInt.get() + ", LongAdder=" + longAdder.sum()); // 6. ForkJoinPool System.out.println("\n6. ForkJoinPool (parallel sum):"); int[] array = new int[1000000]; Arrays.fill(array, 1); class SumTask extends RecursiveTask { private static final int THRESHOLD = 10000; private final int[] array; private final int start, end; SumTask(int[] array, int start, int end) { this.array = array; this.start = start; this.end = end; } @Override protected Long compute() { if (end - start <= THRESHOLD) { long sum = 0; for (int i = start; i < end; i++) sum += array[i]; return sum; } int mid = (start + end) / 2; SumTask left = new SumTask(array, start, mid); SumTask right = new SumTask(array, mid, end); left.fork(); return right.compute() + left.join(); } } ForkJoinPool pool = ForkJoinPool.commonPool(); long sum = pool.invoke(new SumTask(array, 0, array.length)); System.out.println(" Sum of 1,000,000 ones: " + sum); // 7. ConcurrentHashMap System.out.println("\n7. ConcurrentHashMap operations:"); ConcurrentHashMap map = new ConcurrentHashMap<>(); // Atomic operations map.put("A", 1); map.compute("A", (k, v) -> v + 1); map.merge("B", 1, Integer::sum); System.out.println(" Map: " + map); System.out.println("\n=== Concurrency Utilities Demo Complete ==="); } private static void sleep(int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) {} } }

🌊 Streams & Lambdas: Functional Programming

Streams API enables declarative data processing with internal iteration, lazy evaluation, and potential parallel execution. Lambdas provide functional programming capabilities.

🎯 Stream Characteristics

  • No Storage: Streams operate on data sources
  • Functional: Operations don't modify source
  • Lazy: Intermediate operations deferred
  • Consumable: Can only be traversed once
  • Parallelizable: Easy parallel processing

πŸ“‹ Intermediate Operations (Lazy)

  • filter(Predicate): Keep elements matching predicate
  • map(Function): Transform elements
  • flatMap(Function): Flatten nested structures
  • distinct(): Remove duplicates
  • sorted(): Sort elements
  • limit(long): Limit number of elements
  • skip(long): Skip first n elements
  • peek(Consumer): Perform side effect (debugging)

🏁 Terminal Operations (Eager)

  • forEach(Consumer): Iterate over elements
  • collect(Collector): Accumulate into collection
  • toArray(): Convert to array
  • reduce(): Reduce to single value
  • min(), max(): Find extremum
  • count(): Count elements
  • anyMatch(), allMatch(), noneMatch(): Predicate checks
  • findFirst(), findAny(): Find element

πŸ“¦ Common Collectors

  • toList(), toSet(), toMap(): Collect to collections
  • joining(): Concatenate strings with delimiter
  • groupingBy(): Group by classifier
  • partitioningBy(): Partition by predicate
  • summingInt(), averagingInt(): Numeric aggregation
πŸ“Œ Lazy Evaluation πŸ“Œ Parallel Streams πŸ“Œ Method References πŸ“Œ Custom Collectors

Parallel Stream Performance

Parallel streams use ForkJoinPool.commonPool() for parallel execution. They shine for large datasets with CPU-intensive operations. However, overhead of splitting and merging can outweigh benefits for small datasets. Use measure, don't guess - profile before committing to parallel streams. Stateful operations (sorted, distinct) are more expensive in parallel.

// Streams & Lambdas Comprehensive Guide import java.util.*; import java.util.stream.*; import java.util.function.*; public class StreamsDemo { private static class Person { String name; int age; String city; double salary; Person(String name, int age, String city, double salary) { this.name = name; this.age = age; this.city = city; this.salary = salary; } @Override public String toString() { return String.format("%s(%d, %s, $%.0f)", name, age, city, salary); } } public static void main(String[] args) { System.out.println("=== Streams & Lambdas Deep Dive ===\n"); // Sample data List people = Arrays.asList( new Person("Alice", 25, "New York", 75000), new Person("Bob", 30, "London", 82000), new Person("Charlie", 35, "New York", 95000), new Person("Diana", 28, "London", 68000), new Person("Eve", 32, "Paris", 88000), new Person("Frank", 40, "New York", 120000), new Person("Grace", 27, "Paris", 72000) ); // 1. Basic filtering and mapping System.out.println("1. People over 30:"); people.stream() .filter(p -> p.age > 30) .map(p -> p.name) .forEach(name -> System.out.println(" " + name)); // 2. Complex pipeline System.out.println("\n2. Top 3 highest paid in NYC:"); people.stream() .filter(p -> "New York".equals(p.city)) .sorted((a, b) -> Double.compare(b.salary, a.salary)) .limit(3) .forEach(p -> System.out.println(" " + p.name + ": $" + p.salary)); // 3. Grouping and aggregation System.out.println("\n3. Average salary by city:"); Map avgByCity = people.stream() .collect(Collectors.groupingBy( p -> p.city, Collectors.averagingDouble(p -> p.salary) )); avgByCity.forEach((city, avg) -> System.out.printf(" %s: $%.0f%n", city, avg)); // 4. Complex collectors System.out.println("\n4. Statistics by city:"); Map stats = people.stream() .collect(Collectors.groupingBy( p -> p.city, Collectors.summarizingDouble(p -> p.salary) )); stats.forEach((city, stat) -> System.out.printf(" %s - min: $%.0f, max: $%.0f, avg: $%.0f%n", city, stat.getMin(), stat.getMax(), stat.getAverage())); // 5. Reduction operations System.out.println("\n5. Reduction examples:"); double totalSalary = people.stream() .mapToDouble(p -> p.salary) .sum(); System.out.println(" Total salary: $" + totalSalary); Optional highestPaid = people.stream() .reduce((a, b) -> a.salary > b.salary ? a : b); highestPaid.ifPresent(p -> System.out.println(" Highest paid: " + p.name)); // 6. FlatMap - flatten nested structures System.out.println("\n6. FlatMap example:"); List> nested = Arrays.asList( Arrays.asList("Java", "Kotlin"), Arrays.asList("Python", "Ruby"), Arrays.asList("JavaScript", "TypeScript") ); List flat = nested.stream() .flatMap(Collection::stream) .collect(Collectors.toList()); System.out.println(" Flattened: " + flat); // 7. Parallel Stream System.out.println("\n7. Parallel stream performance:"); List numbers = IntStream.rangeClosed(1, 10000000) .boxed() .collect(Collectors.toList()); // Sequential sum long start = System.nanoTime(); long seqSum = numbers.stream() .mapToLong(Integer::longValue) .sum(); long seqTime = System.nanoTime() - start; // Parallel sum start = System.nanoTime(); long parSum = numbers.parallelStream() .mapToLong(Integer::longValue) .sum(); long parTime = System.nanoTime() - start; System.out.println(" Sequential: " + seqTime / 1_000_000 + " ms"); System.out.println(" Parallel: " + parTime / 1_000_000 + " ms"); System.out.println(" Speedup: " + (seqTime / parTime) + "x"); // 8. Custom collector System.out.println("\n8. Custom collector (partition by age):"); Map> byAge = people.stream() .collect(Collectors.partitioningBy(p -> p.age > 30)); System.out.println(" Under 30: " + byAge.get(false).size()); System.out.println(" Over 30: " + byAge.get(true).size()); // 9. Optional operations System.out.println("\n9. Optional patterns:"); people.stream() .filter(p -> p.age > 50) .findFirst() .ifPresentOrElse( p -> System.out.println(" Found: " + p.name), () -> System.out.println(" No one over 50") ); // 10. Method references System.out.println("\n10. Method references:"); people.stream() .map(Person::name) .limit(5) .forEach(System.out::println); // 11. Primitive streams System.out.println("\n11. Primitive streams:"); IntStream.range(1, 10) .map(i -> i * i) .forEach(i -> System.out.print(i + " ")); System.out.println(); // 12. Infinite streams System.out.println("\n12. Infinite stream (first 5 random):"); Random random = new Random(); random.ints() .limit(5) .forEach(i -> System.out.print(i + " ")); System.out.println(); System.out.println("\n=== Streams & Lambdas Demo Complete ==="); } }