ποΈ 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:
- Retrieves the object's vtable pointer from the object header
- Looks up the method slot at a fixed offset
- 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
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
| Operation | ArrayList | LinkedList |
|---|---|---|
| get(int index) | O(1) | O(n) |
| add(E element) | O(1) amortized | O(1) |
| add(int index, E) | O(n) | O(n) |
| remove(int index) | O(n) | O(n) |
| Memory overhead | Low | High (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()andhashCode()consistently - Use
Collections.unmodifiable*()for immutable views - Consider
EnumMapfor enum keys (extremely fast) - Use
LinkedHashMapfor LRU cache implementations
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
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
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
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
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
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 ===");
}
}