-
Notifications
You must be signed in to change notification settings - Fork 1
Threads ‐ Multi Threading
- How to interrupt a running Threads
- Synchronized block. in multi thread concept how do other threads.
- How do we come to know the block is used by other thread so we need to wait.
Multi Threading over shared resource can be achieved in two ways internal-synchronized member and lock variable.
Object level lock vs Class level lock in Java.
In Java, a synchronized block of code can only be executed by one thread at a time. Also, java supports multiple threads to be executed concurrently. This may cause two or more threads to access the same fields or objects at same time.
When a method is declared as synchronized; the thread holds the monitor or lockjava.util.concurrent.locks.Lock object for that method’s object. If another thread is executing the synchronized method, your thread is blocked until that thread releases the monitor.
Object level lock synchronized (this) { ... )
|
Class level lock synchronized (DemoClass.class) { ... }
|
|---|---|
public class DemoClass {
public synchronized void demoMethod() {}
}
public class DemoClass {
public void demoMethod() {
synchronized(this) {
//other thread safe code
}
}
}
public class DemoClass {
private final Object lock = new Object();
public void demoMethod() {
synchronized(lock) {
//other thread safe code
}
}
} |
public class DemoClass { //Method is static
public synchronized static void demoMethod() {}
}
public class DemoClass {
public void demoMethod() { //Acquire lock on .class reference
synchronized(DemoClass.class) {
//other thread safe code
}
}
}
public class DemoClass {
private final static Object lock = new Object();
public void demoMethod() { //Lock object is static
synchronized(lock) {
//other thread safe code
}
}
} |
Java synchronized keyword is re-entrant in nature it means if a synchronized method calls another synchronized method which requires same lock then current thread which is holding lock can enter into that method without acquiring lock.
Lock framework package
java.util.concurrent.locksSince:1.5
The Lock framework in java.util.concurrent.lock is an abstraction for locking, allowing for lock implementations that are implemented as Java classes rather than as a language feature. It makes room for multiple implementations of Lock, which may have different scheduling algorithms, performance characteristics, or locking semantics.
Class ReentrantLock
Because the thread owns the lock it will allow multiple calls to lock(), so it re-enter the lock. This can be achieved with a reference count so it doesn't has to acquire lock again.stackoverflow
private static ReentrantLock lock = new ReentrantLock();
void accessResource() {
lock.lock();
if( checkSomeCondition() ) {
accessResource();
}
lock.unlock();
}Form Java DOC: This lock supports a maximum of 2147483647 recursive locks by the same thread. Attempts to exceed this limit result in Error throws from locking methods.
private static final ConcurrentHashMap<ThreadGroup, ReentrantLock> locks = new ConcurrentHashMap<>();
private static ReentrantLock getLock() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
return locks.computeIfAbsent(
group, g -> new ReentrantLock()
);
}ReentrantLock.lock() waits indefinitely until the lock becomes available. There is no default timeout. If we need bounded waiting, we can use tryLock(timeout, TimeUnit). We can also use tryLock() for a non-blocking attempt.
With ReentrantLock, there is no default maximum waiting time when you use:
lock() — waits indefinitely |
tryLock(long timeout, TimeUnit unit) — You can override the waiting time with |
tryLock() without waiting. — Try to acquire the lock. If somebody else has it, don't wait. |
|---|---|---|
lock.lock();
try {
// critical section
} finally {
lock.unlock();
} |
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// lock acquired
// DB operation
} finally {
lock.unlock();
}
} else {
// Could not acquire lock within 5 seconds
System.out.println("Lock timeout");
} |
if (lock.tryLock()) {
try {
// acquired
} finally {
lock.unlock();
}
} else {
// immediately continue
} |
ReentrantLockCacheExample - Multi-threaded Cache Synchronization Patterns
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* <h2>ReentrantLockCacheExample - Multi-threaded Cache Synchronization Patterns</h2>
*
* <h3>ARCHITECTURAL PROBLEM STATEMENT:</h3>
* <p>
* In high-concurrency systems (e.g., payment processing), multiple threads frequently access the same cached
* resource. This leads to a "Thundering Herd" problem where:
* <ul>
* <li><b>Without Locking:</b> Race conditions cause multiple threads to fetch the same data from DB
* simultaneously, wasting resources and causing data inconsistency.</li>
* <li><b>With Global Lock:</b> All threads (even different groups) are serialized, killing parallelism
* and causing bottlenecks.</li>
* <li><b>Thread Groups:</b> Different thread groups (e.g., ForeignPayment vs DomesticPayment) should not
* block each other even if they access the same resource key.</li>
* </ul>
* </p>
*
* <h3>SOLUTION OVERVIEW:</h3>
* <p>
* This class demonstrates THREE synchronization patterns to overcome these challenges:
* </p>
*
* <h4>SCENARIO 1: Global Lock (Single Coarse-grained Lock)</h4>
* <ul>
* <li><b>Use Case:</b> Simple systems, low concurrency, or when all threads access same resource.</li>
* <li><b>Mechanism:</b> One ReentrantLock for entire cache, all threads serialized.</li>
* <li><b>Pros:</b> Simple, no deadlock risk, easy to understand.</li>
* <li><b>Cons:</b> Poor scalability, all threads wait even if accessing different keys.</li>
* </ul>
*
* <h4>SCENARIO 2: Per-Key Lock (Fine-grained Locking with Thread Groups)</h4>
* <ul>
* <li><b>Use Case:</b> High concurrency with multiple thread groups accessing different resources.</li>
* <li><b>Mechanism:</b> Per-key lock stored in format [THREAD_GROUP_NAME:KEY_NAME].</li>
* <li><b>Pros:</b> Threads accessing different keys don't block; supports thread groups.</li>
* <li><b>Cons:</b> More complex, potential for lock proliferation, requires careful management.</li>
* </ul>
*
* <h4>SCENARIO 3: Synchronized Method (JVM-level Synchronization)</h4>
* <ul>
* <li><b>Use Case:</b> Legacy systems, when ReentrantLock not available, or for method-level atomicity.</li>
* <li><b>Mechanism:</b> Using 'synchronized' keyword on method or object instance.</li>
* <li><b>Pros:</b> Built-in, no external library needed, automatic lock release.</li>
* <li><b>Cons:</b> Cannot upgrade lock (e.g., write lock), no timeout/interrupt support, holds lock
* until method exit.</li>
* </ul>
*
* <h3>KEY PATTERNS DEMONSTRATED:</h3>
* <ul>
* <li><b>Double-Check Locking:</b> Check cache before lock, acquire lock, then check again.
* This prevents redundant DB calls when thread waits for lock.</li>
* <li><b>ConcurrentHashMap for Lock Storage:</b> Thread-safe storage for per-key locks to prevent
* race conditions during lock creation.</li>
* <li><b>Try-Finally Pattern:</b> Ensures lock is released even if exception occurs.</li>
* </ul>
*
* @author Architecture Team
* @version 2.0 - Multi-scenario synchronization patterns
* @see java.util.concurrent.locks.ReentrantLock
*/
public class ReentrantLockCacheExample {
/**
* Enumeration defining synchronization strategies to test.
* Each scenario represents a different locking mechanism suitable for different use cases.
*/
public enum LockStrategy {
/**
* <b>SCENARIO 1 - Global Lock:</b>
* Single ReentrantLock for all threads and all keys.
* All threads are serialized regardless of key or thread group.
* Best for: Simple systems, low concurrency, strong consistency requirements.
*/
GLOBAL_LOCK("Scenario 1: Global Lock (All threads serialized)"),
/**
* <b>SCENARIO 2 - Per-Key Lock with Thread Groups:</b>
* Individual ReentrantLock per key-threadgroup combination.
* Format: [THREAD_GROUP_NAME:KEY_NAME]
* Threads from different groups accessing same key use different locks.
* Best for: High concurrency, independent thread groups (ForeignPayment vs DomesticPayment).
*/
PER_KEY_WITH_THREAD_GROUP("Scenario 2: Per-Key Lock with Thread Groups (Granular control)"),
/**
* <b>SCENARIO 3 - Synchronized Method:</b>
* Using synchronized keyword instead of ReentrantLock.
* JVM manages lock acquisition/release automatically.
* Best for: Legacy code, simple scenarios, when ReentrantLock not needed.
*/
SYNCHRONIZED_METHOD("Scenario 3: Synchronized Method (JVM-managed locking)");
private final String description;
LockStrategy(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
/**
* <b>LockManager - Static Utility Class:</b>
* Centralized management of all locks across scenarios.
* Provides factory methods to obtain appropriate locks based on strategy.
* Implements the Repository/Factory pattern for lock management.
*
* <b>Why separate class?</b>
* <ul>
* <li>Single Responsibility: Manages all lock lifecycle and creation.</li>
* <li>Testability: Can mock or inject different implementations.</li>
* <li>Reusability: Other classes can use this utility.</li>
* </ul>
*/
static class LockManager {
// Scenario 1: Global lock - shared by all threads
private static final ReentrantLock GLOBAL_LOCK = new ReentrantLock();
// Scenario 2: Per-key locks stored with thread group information
// Format: "THREAD_GROUP_NAME:KEY"
private static final Map<String, ReentrantLock> PER_KEY_LOCKS = new ConcurrentHashMap<>();
// Scenario 3: Synchronized uses object monitor - dummy object for locking
private static final Object SYNC_LOCK_OBJECT = new Object();
/**
* Get lock based on strategy.
*
* @param strategy The locking strategy to use.
* @param key The resource key being accessed.
* @return ReentrantLock instance appropriate for the strategy.
*/
static ReentrantLock getLock(LockStrategy strategy, String key) {
return switch (strategy) {
case GLOBAL_LOCK -> GLOBAL_LOCK;
case PER_KEY_WITH_THREAD_GROUP -> {
String lockKey = getLockKey(key);
yield PER_KEY_LOCKS.computeIfAbsent(lockKey, k -> new ReentrantLock());
}
case SYNCHRONIZED_METHOD -> null; // Synchronized uses different mechanism
};
}
/**
* Generate composite lock key including thread group information.
* This ensures different thread groups use different locks even for same key.
*
* @param key The resource key.
* @return Composite key: "THREAD_GROUP_NAME:KEY"
*/
private static String getLockKey(String key) {
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
String groupName = threadGroup != null ? threadGroup.getName() : "DEFAULT";
return groupName + ":" + key;
}
/**
* Get the synchronized object for SCENARIO 3.
*
* @return Object used as monitor for synchronized blocks.
*/
static Object getSyncObject() {
return SYNC_LOCK_OBJECT;
}
/**
* Clear all locks (useful for testing/resetting state).
*/
static void clearLocks() {
PER_KEY_LOCKS.clear();
}
/**
* Get current lock statistics for monitoring.
*
* @return Statistics about locks in use.
*/
static String getStatistics() {
return String.format("Per-Key Locks in use: %d", PER_KEY_LOCKS.size());
}
}
// Shared cache for all scenarios
private static final Map<String, String> mapCache = new ConcurrentHashMap<>();
/**
* Main entry point demonstrating all three scenarios.
* Each scenario runs with different thread groups to show lock behavior.
*
* @param args Command-line arguments (unused).
*/
public static void main(String[] args) {
System.out.println("\n" + "=".repeat(80));
System.out.println("MULTI-THREADED CACHE SYNCHRONIZATION PATTERNS DEMO");
System.out.println("=".repeat(80) + "\n");
// Test each strategy
testStrategy(LockStrategy.GLOBAL_LOCK);
System.out.println("\n" + "-".repeat(80) + "\n");
testStrategy(LockStrategy.PER_KEY_WITH_THREAD_GROUP);
System.out.println("\n" + "-".repeat(80) + "\n");
testStrategy(LockStrategy.SYNCHRONIZED_METHOD);
System.out.println("\n" + "=".repeat(80));
System.out.println("DEMO COMPLETED");
System.out.println("=".repeat(80) + "\n");
}
/**
* <b>testStrategy:</b>
* Comprehensive test for a given locking strategy.
* Simulates two thread groups (ForeignPayment and DomesticPayment) accessing
* different keys in parallel to demonstrate lock behavior.
*
* <b>Test Scenario:</b>
* <ul>
* <li>Thread Group 1 (ForeignPayment): 20 threads accessing key "EMP001"</li>
* <li>Thread Group 2 (DomesticPayment): 20 threads accessing key "EMP002"</li>
* <li>Both groups run in parallel</li>
* <li>Observe: How many times DB is fetched and lock contention</li>
* </ul>
*
* @param strategy The locking strategy to test.
*/
private static void testStrategy(LockStrategy strategy) {
mapCache.clear();
LockManager.clearLocks();
System.out.println("Testing: " + strategy.getDescription());
System.out.println("Lock Manager: " + LockManager.getStatistics());
// Create thread groups for simulation
ThreadGroup foreignPaymentGroup = new ThreadGroup("ForeignPayment");
ThreadGroup domesticPaymentGroup = new ThreadGroup("DomesticPayment");
// Simulate employee data for two different payment types
List<String> foreignPaymentKeys = new ArrayList<>();
for (int i = 0; i < 20; i++) {
foreignPaymentKeys.add("EMP001");
}
List<String> domesticPaymentKeys = new ArrayList<>();
for (int i = 0; i < 20; i++) {
domesticPaymentKeys.add("EMP002");
}
List<List<String>> allThreadGroups = new ArrayList<>();
allThreadGroups.add(foreignPaymentKeys);
allThreadGroups.add(domesticPaymentKeys);
System.out.println("Starting parallel thread groups processing...");
long startTime = System.currentTimeMillis();
// Both thread groups process in parallel
allThreadGroups.parallelStream()
.forEach(keys -> {
processThreadGroup(keys, strategy);
});
long duration = System.currentTimeMillis() - startTime;
System.out.println("\nFinal Cache State: " + mapCache);
System.out.println("Total Duration: " + duration + "ms");
System.out.println("Lock Manager Stats: " + LockManager.getStatistics());
}
/**
* <b>processThreadGroup:</b>
* Process a group of tasks using specified locking strategy.
* Each task runs in a separate thread accessing the given key.
*
* @param keys List of keys to process (multiple threads, same key).
* @param strategy The locking strategy to use.
*/
private static void processThreadGroup(List<String> keys, LockStrategy strategy) {
System.out.println("\n" + "→".repeat(40) +
"Starting " + Thread.currentThread().getThreadGroup().getName() +
" → " + "←".repeat(40));
keys.parallelStream()
.forEach(key -> {
if (strategy == LockStrategy.SYNCHRONIZED_METHOD) {
getDataWithSynchronized(key);
} else {
getDataWithReentrantLock(key, strategy);
}
System.out.println(
Thread.currentThread().getName()
+ " [" + Thread.currentThread().getThreadGroup().getName() + "]"
+ " -> Final Data: " + mapCache.get(key)
);
});
System.out.println("←".repeat(40) +
"Completed " + Thread.currentThread().getThreadGroup().getName() +
" → " + "→".repeat(40));
}
/**
* <b>getDataWithReentrantLock:</b>
* Fetch data from cache using ReentrantLock with double-check locking pattern.
*
* <h4>Algorithm (Double-Check Locking):</h4>
* <ol>
* <li><b>First Check (No Lock):</b> Check cache without lock for performance.</li>
* <li><b>Acquire Lock:</b> If miss, acquire lock to proceed to DB fetch.</li>
* <li><b>Second Check (With Lock):</b> Check cache again; another thread may have fetched
* while this thread waited for lock. This prevents duplicate DB calls.</li>
* <li><b>DB Fetch:</b> Only if data still missing, fetch from DB.</li>
* <li><b>Cache Store:</b> Store fetched data in cache for other threads.</li>
* </ol>
*
* <h4>Why Double-Check?</h4>
* Without second check, if Thread-A waits for lock while Thread-B fetches data,
* Thread-A would also fetch duplicate data causing wasted DB calls and data anomalies.
*
* @param key The resource key to fetch.
* @param strategy The locking strategy (GLOBAL_LOCK or PER_KEY_WITH_THREAD_GROUP).
*/
private static String getDataWithReentrantLock(String key, LockStrategy strategy) {
// ============== FIRST CHECK: No lock ==============
String data = mapCache.get(key);
if (data != null) {
logThreadInfo("CACHE HIT (No Lock Needed)", key);
return data;
}
logThreadInfo("CACHE MISS -> Acquiring Lock", key);
// ============== ACQUIRE LOCK ==============
ReentrantLock lock = LockManager.getLock(strategy, key);
lock.lock();
try {
logThreadInfo("LOCK ACQUIRED", key);
// ============== SECOND CHECK: With lock ==============
// CRITICAL: Another thread may have fetched data while this thread waited for lock
data = mapCache.get(key);
if (data != null) {
logThreadInfo("DATA FOUND AFTER ACQUIRING LOCK (Another thread fetched)", key);
return data;
}
// ============== DB FETCH: Only one thread executes this ==============
logThreadInfo("DB FETCH START", key);
data = fetchFromDB(key);
logThreadInfo("DB FETCH COMPLETED: " + data, key);
// ============== CACHE STORE ==============
mapCache.put(key, data);
logThreadInfo("CACHE UPDATED", key);
return data;
} finally {
// ============== RELEASE LOCK ==============
lock.unlock();
logThreadInfo("LOCK RELEASED", key);
}
}
/**
* <b>getDataWithSynchronized:</b>
* Fetch data from cache using Java's synchronized keyword.
* Uses the same double-check locking pattern but with synchronized.
*
* <h4>Differences from ReentrantLock:</h4>
* <ul>
* <li>JVM manages lock acquisition/release automatically.</li>
* <li>Cannot upgrade lock (downgrade: ReadWriteLock not applicable).</li>
* <li>Lock held until method exit (cannot release early).</li>
* <li>No tryLock(), no timeout support.</li>
* </ul>
*
* @param key The resource key to fetch.
*/
private static String getDataWithSynchronized(String key) {
// ============== FIRST CHECK: No lock ==============
String data = mapCache.get(key);
if (data != null) {
logThreadInfo("CACHE HIT (No Lock Needed)", key);
return data;
}
logThreadInfo("CACHE MISS -> Acquiring Synchronized Lock", key);
// ============== SYNCHRONIZED BLOCK ==============
synchronized (LockManager.getSyncObject()) {
logThreadInfo("SYNCHRONIZED LOCK ACQUIRED", key);
// ============== SECOND CHECK: Inside synchronized ==============
data = mapCache.get(key);
if (data != null) {
logThreadInfo("DATA FOUND AFTER SYNCHRONIZED LOCK", key);
return data;
}
// ============== DB FETCH ==============
logThreadInfo("DB FETCH START (Synchronized)", key);
data = fetchFromDB(key);
logThreadInfo("DB FETCH COMPLETED: " + data, key);
// ============== CACHE STORE ==============
mapCache.put(key, data);
logThreadInfo("CACHE UPDATED (Synchronized)", key);
return data;
}
// Lock automatically released here
}
/**
* <b>fetchFromDB:</b>
* Simulates a database call with latency.
* This is the expensive operation that locking protects against.
*
* <h4>Why Expensive?</h4>
* <ul>
* <li>Network latency: 100-500ms typical</li>
* <li>Query execution: 50-200ms typical</li>
* <li>Serialization: 10-50ms typical</li>
* <li>Total: Often 500-1000ms minimum</li>
* </ul>
*
* <b>Observation:</b> With 20 threads and 10 second DB call, without locking
* you'd have 20 × 10s = 200s wasted time. Good locking reduces this to ~10s.
*
* @param key The resource key to fetch from database.
* @return Simulated database value: "DB_DATA_FOR_[KEY]"
*/
private static String fetchFromDB(String key) {
Thread currentThread = Thread.currentThread();
System.err.println("\t[" + currentThread.getName() + "] Fetching From DB...");
try {
// Simulate realistic DB call latency (10 seconds)
Thread.sleep(10000);
} catch (InterruptedException e) {
currentThread.interrupt();
Thread.currentThread().interrupt();
}
System.err.println("\t[" + currentThread.getName() + "] DB Fetch Completed.");
return "DB_DATA_FOR_" + key;
}
/**
* <b>logThreadInfo:</b>
* Utility method to log thread execution details in consistent format.
* Includes thread name, thread group, and operation description.
*
* @param operation Description of the operation.
* @param key The resource key being accessed.
*/
private static void logThreadInfo(String operation, String key) {
System.out.println(
String.format(
"%-15s [%-20s] -> %-50s | Key: %s",
Thread.currentThread().getName(),
Thread.currentThread().getThreadGroup().getName(),
operation,
key
)
);
}
}Cache Data Fetch Problem Demo - Race Condition & Performance Degradation
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <h2>Cache Data Fetch Problem Demo - Race Condition & Performance Degradation</h2>
*
* <h3>REAL-WORLD PROBLEM STATEMENT:</h3>
* <p>
* In a high-concurrency database caching system, we encounter a critical performance and
* data consistency issue:
* </p>
*
* <h4>Scenario: Employee Record Lookup System</h4>
* <p>
* Multiple users request employee details (EMP_ID: "EMP123") within milliseconds:
* </p>
* <pre>
* Time: T1 Time: T2 Time: T3 Time: T4
* Thread-1 Thread-2 Thread-3 Thread-4
* ├─ Check Cache ├─ Check Cache ├─ Check Cache ├─ Check Cache
* │ (MISS) │ (MISS) │ (MISS) │ (MISS)
* │ │ │ │
* ├─ DB Call ├─ DB Call ├─ DB Call ├─ DB Call
* │ START │ START │ START │ START
* │ (10sec) │ (10sec) │ (10sec) │ (10sec)
* │ │ │ │
* ├─ Got Data ├─ Got Data ├─ Got Data ├─ Got Data
* │ "JOHN_V1" │ "JOHN_V2" │ "JOHN_V3" │ "JOHN_V4"
* │ │ │ │
* ├─ Cache[EMP123] ├─ Cache[EMP123] ├─ Cache[EMP123] ├─ Cache[EMP123]
* │ = "JOHN_V1" │ = "JOHN_V2" │ = "JOHN_V3" │ = "JOHN_V4"
* │ │ │ │
* └─ Return └─ Return └─ Return └─ Return
* "JOHN_V1" "JOHN_V2" "JOHN_V3" "JOHN_V4"
* </pre>
*
* <h3>THE PROBLEMS THIS CAUSES:</h3>
*
* <h4>1. THUNDERING HERD - Redundant DB Calls</h4>
* <p>
* <b>Problem:</b> All 4 threads independently call the database for the SAME key.
* Instead of 1 DB call, we have 4 DB calls × 10 seconds = 40 seconds wasted.
* </p>
* <pre>
* Expected Cost: 1 DB call × 10sec = 10 seconds ✓
* Actual Cost: 4 DB calls × 10sec = 40 seconds ✗
* Waste: 300% performance degradation
* </pre>
*
* <h4>2. DATA INCONSISTENCY - Cache Override Race Condition</h4>
* <p>
* <b>Problem:</b> Each thread fetches potentially different data versions (if DB was
* updated between calls), then ALL threads override the cache. The last thread to write wins.
* This causes data anomalies where different threads see different values.
* </p>
* <pre>
* Thread-1 writes: Cache[EMP123] = "JOHN_V1"
* Thread-2 writes: Cache[EMP123] = "JOHN_V2" (overwrites Thread-1's data)
* Thread-3 writes: Cache[EMP123] = "JOHN_V3" (overwrites Thread-2's data)
* Thread-4 writes: Cache[EMP123] = "JOHN_V4" (overwrites Thread-3's data)
*
* Result: Cache contains "JOHN_V4" (version from Thread-4)
* But Thread-1 may have already used "JOHN_V1" and committed transaction
* → DATA INCONSISTENCY ACROSS TRANSACTIONS
* </pre>
*
* <h4>3. CASCADING LOCK CONTENTION (Why Synchronized is NOT the solution)</h4>
* <p>
* <b>Naive Solution Attempt:</b> Use synchronized keyword to block all threads.
* </p>
* <pre>
* synchronized(globalLock) {
* if (cache.get(key) == null) {
* cache.put(key, fetchFromDB(key));
* }
* }
* </pre>
* <b>Problems with Global Synchronized:</b>
* <ul>
* <li><b>Coarse-Grained Locking:</b> Even if Thread-1 fetches EMP123, Thread-2 cannot
* fetch EMP456 (different key). ALL threads are serialized.</li>
* <li><b>Scalability Killer:</b> With 100 concurrent users and 10 different employee IDs,
* you get massive lock contention. Throughput collapses.</li>
* <li><b>No Lock Downgrade:</b> Synchronized cannot differentiate between "read lock" and
* "write lock" scenarios. Unnecessarily exclusive.</li>
* </ul>
* <pre>
* Thread-1 locks: Cache[EMP123] fetch starts
* Thread-2 waits: Cache[EMP456] fetch blocked ← Different key! But still waiting!
* Thread-3 waits: Cache[EMP789] fetch blocked ← Different key! But still waiting!
*
* Result: All threads serialized even though they access DIFFERENT keys
* Throughput: 20 req/sec instead of potential 200 req/sec
* </pre>
*
* <h4>4. MEMORY OVERHEAD - Lock Proliferation</h4>
* <p>
* <b>Problem:</b> If cache contains 1 million keys and system creates 1 million locks,
* memory usage explodes. Each ReentrantLock ~48 bytes = 48 MB just for locks.
* </p>
*
* <h3>THE ARCHITECTURAL SOLUTION:</h3>
*
* <h4>Per-Key ReentrantLock (Fine-Grained Locking)</h4>
* <p>
* <b>Strategy:</b> Use a separate lock for EACH cache key. Only threads accessing
* the SAME key are blocked. Threads accessing different keys run in parallel.
* </p>
* <pre>
* Lock Map: {
* "EMP123" → ReentrantLock-1,
* "EMP456" → ReentrantLock-2,
* "EMP789" → ReentrantLock-3
* }
*
* Thread-1 locks: Lock-1 (EMP123) → Fetching EMP123 from DB
* Thread-2 waits: Lock-1 (EMP123) ← Blocked (same key as Thread-1)
* Thread-3 proceeds: Lock-2 (EMP456) ← NOT BLOCKED (different key!)
* Thread-4 proceeds: Lock-3 (EMP789) ← NOT BLOCKED (different key!)
*
* Result: Threads accessing SAME key serialize for cache integrity
* Threads accessing DIFFERENT keys run in parallel
* Throughput: 200 req/sec (optimal)
* </pre>
*
* <h4>Why ReentrantLock > Synchronized?</h4>
* <table border="1">
* <tr>
* <th>Feature</th>
* <th>Synchronized</th>
* <th>ReentrantLock</th>
* </tr>
* <tr>
* <td>Lock Granularity</td>
* <td>Object-level (whole method/block)</td>
* <td>Per-key (fine-grained)</td>
* </tr>
* <tr>
* <td>Fairness</td>
* <td>Not guaranteed</td>
* <td>Can enforce fairness</td>
* </tr>
* <tr>
* <td>Timeout Support</td>
* <td>No</td>
* <td>tryLock(timeout, unit)</td>
* </tr>
* <tr>
* <td>Interruptibility</td>
* <td>No</td>
* <td>lockInterruptibly()</td>
* </tr>
* <tr>
* <td>Condition Variables</td>
* <td>No (wait/notify only)</td>
* <td>Yes (Condition API)</td>
* </tr>
* </table>
*
* <h3>METRICS DEMONSTRATING THE PROBLEM & SOLUTION:</h3>
* <pre>
* ┌─────────────────────────┬──────────────┬──────────────┐
* │ Metric │ With Problem │ With Solution│
* ├─────────────────────────┼──────────────┼──────────────┤
* │ DB Calls for 4 threads │ 4 calls │ 1 call │
* │ Total DB Time │ 40 seconds │ 10 seconds │
* │ Cache Coherency │ NO (4 writes)│ YES (1 write)│
* │ Thread Throughput │ 5 req/sec │ 50 req/sec │
* │ Lock Contention │ 100% (all) │ 25% (same key)
* │ Scalability │ O(n²) │ O(1) per key │
* └─────────────────────────┴──────────────┴──────────────┘
* </pre>
*
* @author Architecture Team
* @version 1.0 - Real-world cache fetch problem demonstration
* @see java.util.concurrent.locks.ReentrantLock
* @see java.util.concurrent.ConcurrentHashMap
*/
public class CacheDataFetchProblemDemo {
/**
* Enumeration to select which scenario to run.
*/
public enum DemoMode {
/**
* Demonstrates the PROBLEM: Without proper locking, multiple threads
* fetch and override cache data causing redundant DB calls and inconsistency.
*/
PROBLEM("❌ PROBLEM: Uncontrolled Concurrent Cache Updates (Without Locking)"),
/**
* Demonstrates the SOLUTION: With per-key ReentrantLock, only one thread
* fetches for a key while others use cached data.
*/
SOLUTION("✓ SOLUTION: Per-Key ReentrantLock (With Locking)");
private final String description;
DemoMode(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
/**
* <b>CacheManager - Problem Scenario (NO LOCKING):</b>
* Demonstrates the concurrency problem without proper synchronization.
* Multiple threads can simultaneously fetch and override cache data.
*/
static class CacheManagerWithoutLocking {
private final Map<String, String> cache = new ConcurrentHashMap<>();
private final AtomicInteger dbCallCount = new AtomicInteger(0);
private final AtomicInteger cacheWriteCount = new AtomicInteger(0);
private final List<String> dbCallLog = Collections.synchronizedList(new ArrayList<>());
String getData(String key) throws InterruptedException {
String cachedData = cache.get(key);
if (cachedData != null) {
logInfo(key, "CACHE HIT (No DB call needed)");
return cachedData;
}
logInfo(key, "CACHE MISS - Making DB call...");
dbCallCount.incrementAndGet();
// Simulate all threads independently calling database
// Each thread may get slightly different data if DB is being updated
String dbData = simulateDBCall(key);
logInfo(key, "DB returned: " + dbData);
// PROBLEM: All threads write to cache, last write wins
cache.put(key, dbData);
cacheWriteCount.incrementAndGet();
dbCallLog.add(Thread.currentThread().getName() + " fetched from DB");
logInfo(key, "Cache updated with: " + dbData);
return dbData;
}
void resetCache() {
cache.clear();
dbCallCount.set(0);
cacheWriteCount.set(0);
dbCallLog.clear();
}
int getDBCallCount() {
return dbCallCount.get();
}
int getCacheWriteCount() {
return cacheWriteCount.get();
}
List<String> getDBCallLog() {
return new ArrayList<>(dbCallLog);
}
Map<String, String> getCacheState() {
return new HashMap<>(cache);
}
}
/**
* <b>CacheManager - Solution Scenario (WITH PER-KEY LOCKING):</b>
* Demonstrates the solution using fine-grained ReentrantLock per cache key.
* Only one thread fetches for a key; others wait for lock or use cached data.
*/
static class CacheManagerWithPerKeyLocking {
private final Map<String, String> cache = new ConcurrentHashMap<>();
private final Map<String, ReentrantLock> keyLocks = new ConcurrentHashMap<>();
private final AtomicInteger dbCallCount = new AtomicInteger(0);
private final AtomicInteger cacheWriteCount = new AtomicInteger(0);
private final List<String> dbCallLog = Collections.synchronizedList(new ArrayList<>());
/**
* Get lock for specific key. Uses computeIfAbsent to ensure
* all threads accessing the same key get the same lock object.
*/
private ReentrantLock getLockForKey(String key) {
return keyLocks.computeIfAbsent(key, k -> new ReentrantLock());
}
String getData(String key) throws InterruptedException {
// ============== FIRST CHECK: No lock ==============
String cachedData = cache.get(key);
if (cachedData != null) {
logInfo(key, "CACHE HIT (No lock, no DB call)");
return cachedData;
}
logInfo(key, "CACHE MISS - Acquiring per-key lock...");
// ============== ACQUIRE PER-KEY LOCK ==============
ReentrantLock keyLock = getLockForKey(key);
keyLock.lock();
try {
logInfo(key, "Lock acquired - checking cache again...");
// ============== SECOND CHECK: With lock ==============
// Another thread may have fetched while this thread waited for lock
cachedData = cache.get(key);
if (cachedData != null) {
logInfo(key, "DATA FOUND (Another thread already fetched): " + cachedData);
return cachedData;
}
// ============== DB FETCH: Only one thread executes ==============
logInfo(key, "DB call starting (only thread with this key lock)...");
dbCallCount.incrementAndGet();
String dbData = simulateDBCall(key);
logInfo(key, "DB returned: " + dbData);
// ============== CACHE WRITE: Controlled by lock ==============
cache.put(key, dbData);
cacheWriteCount.incrementAndGet();
dbCallLog.add(Thread.currentThread().getName() + " fetched from DB");
logInfo(key, "Cache updated with: " + dbData);
return dbData;
} finally {
logInfo(key, "Releasing per-key lock...");
keyLock.unlock();
}
}
void resetCache() {
cache.clear();
keyLocks.clear();
dbCallCount.set(0);
cacheWriteCount.set(0);
dbCallLog.clear();
}
int getDBCallCount() {
return dbCallCount.get();
}
int getCacheWriteCount() {
return cacheWriteCount.get();
}
List<String> getDBCallLog() {
return new ArrayList<>(dbCallLog);
}
Map<String, String> getCacheState() {
return new HashMap<>(cache);
}
int getActiveLockCount() {
return keyLocks.size();
}
}
// Static instances for demos
private static final CacheManagerWithoutLocking problemCache = new CacheManagerWithoutLocking();
private static final CacheManagerWithPerKeyLocking solutionCache = new CacheManagerWithPerKeyLocking();
/**
* Main entry point for demonstration.
*
* @param args Command line arguments (unused).
*/
public static void main(String[] args) throws InterruptedException {
System.out.println("\n" + "═".repeat(100));
System.out.println("CACHE DATA FETCH PROBLEM & SOLUTION DEMONSTRATION");
System.out.println("═".repeat(100) + "\n");
reproduceProblem();
System.out.println("\n\n" + "═".repeat(100) + "\n");
solutionOfProblem();
System.out.println("\n" + "═".repeat(100));
System.out.println("DEMONSTRATION COMPLETED");
System.out.println("═".repeat(100) + "\n");
System.out.println("\n" + "═".repeat(100));
architectObservations();
}
/**
* <b>reproduceProblem():</b>
* Demonstrates the concurrency problem WITHOUT proper locking.
*
* <h4>Scenario:</h4>
* 50 threads attempt to fetch employee data with key "EMP_ID_001" from cache.
* Since cache is empty, all 50 threads will:
* <ol>
* <li>Detect cache miss</li>
* <li>Independently call database</li>
* <li>Each thread updates cache with its own version of data</li>
* </ol>
*
* <h4>Expected Result (Correct):</h4>
* <ul>
* <li>1 DB call (one fetch)</li>
* <li>1 cache write (one update)</li>
* <li>All threads return same data</li>
* </ul>
*
* <h4>Actual Result (Problem):</h4>
* <ul>
* <li>50 DB calls (thundering herd - waste)</li>
* <li>50 cache writes (race condition - last write wins)</li>
* <li>Potential data inconsistency</li>
* </ul>
*
* @throws InterruptedException If thread is interrupted.
*/
public static void reproduceProblem() throws InterruptedException {
System.out.println(DemoMode.PROBLEM.getDescription());
System.out.println("-".repeat(100) + "\n");
problemCache.resetCache();
String key = "EMP_ID_001";
int threadCount = 50;
System.out.println("Scenario: " + threadCount + " threads accessing SAME cache key: \"" + key + "\"");
System.out.println("Expected DB Calls: 1 | Expected Cache Writes: 1\n");
System.out.println("Creating and starting " + threadCount + " threads...\n");
long startTime = System.currentTimeMillis();
// Create and start 50 threads accessing same cache key
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(() -> {
try {
problemCache.getData(key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "ProblemThread-" + i);
threads.add(thread);
thread.start();
}
// Wait for all threads to complete
for (Thread thread : threads) {
thread.join();
}
long duration = System.currentTimeMillis() - startTime;
// Analyze results
System.out.println("\n" + "─".repeat(100));
System.out.println("PROBLEM ANALYSIS:");
System.out.println("─".repeat(100));
int dbCalls = problemCache.getDBCallCount();
int cacheWrites = problemCache.getCacheWriteCount();
System.out.println("\n📊 METRICS:");
System.out.println(" Actual DB Calls: " + dbCalls + " ❌ (Expected: 1)");
System.out.println(" Actual Cache Writes: " + cacheWrites + " ❌ (Expected: 1)");
System.out.println(" Redundant DB Calls: " + (dbCalls - 1) + " (Wasted " + (dbCalls - 1) * 10 + " seconds!)");
System.out.println(" Total Execution Time: " + duration + "ms");
System.out.println("\n🔴 PROBLEMS IDENTIFIED:");
System.out.println(" 1. Thundering Herd: " + threadCount + " threads made " + dbCalls + " DB calls for same data");
System.out.println(" 2. Resource Waste: " + (dbCalls - 1) + " unnecessary database fetches");
System.out.println(" 3. Cache Thrashing: " + cacheWrites + " cache updates (last write wins)");
System.out.println(" 4. Data Consistency: Potential anomalies if DB was updated between calls");
System.out.println("\n📝 DB Call Log:");
problemCache.getDBCallLog().stream()
.distinct()
.forEach(log -> System.out.println(" • " + log));
System.out.println("\n📦 Final Cache State: " + problemCache.getCacheState());
}
/**
* <b>solutionOfProblem():</b>
* Demonstrates the solution using per-key ReentrantLock.
*
* <h4>Scenario:</h4>
* Same 50 threads attempt to fetch employee data with key "EMP_ID_001".
* With per-key locking:
* <ol>
* <li>First thread acquires lock for key "EMP_ID_001"</li>
* <li>Other 49 threads wait for the lock</li>
* <li>First thread fetches from DB and updates cache</li>
* <li>Other threads get lock one by one, but find cache already populated</li>
* <li>They return cached data without DB call</li>
* </ol>
*
* <h4>Result (Solution):</h4>
* <ul>
* <li>1 DB call (optimal - one fetch)</li>
* <li>1 cache write (one update - no races)</li>
* <li>All threads return same data (consistency)</li>
* <li>~10 seconds total (vs 40+ seconds without locking)</li>
* </ul>
*
* @throws InterruptedException If thread is interrupted.
*/
public static void solutionOfProblem() throws InterruptedException {
System.out.println(DemoMode.SOLUTION.getDescription());
System.out.println("-".repeat(100) + "\n");
solutionCache.resetCache();
String key = "EMP_ID_001";
int threadCount = 50;
System.out.println("Scenario: " + threadCount + " threads accessing SAME cache key: \"" + key + "\"");
System.out.println("With Per-Key ReentrantLock implementation");
System.out.println("Expected DB Calls: 1 | Expected Cache Writes: 1\n");
System.out.println("Creating and starting " + threadCount + " threads...\n");
long startTime = System.currentTimeMillis();
// Create and start 50 threads accessing same cache key with locking
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(() -> {
try {
solutionCache.getData(key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "SolutionThread-" + i);
threads.add(thread);
thread.start();
}
// Wait for all threads to complete
for (Thread thread : threads) {
thread.join();
}
long duration = System.currentTimeMillis() - startTime;
// Analyze results
System.out.println("\n" + "─".repeat(100));
System.out.println("SOLUTION ANALYSIS:");
System.out.println("─".repeat(100));
int dbCalls = solutionCache.getDBCallCount();
int cacheWrites = solutionCache.getCacheWriteCount();
System.out.println("\n📊 METRICS:");
System.out.println(" Actual DB Calls: " + dbCalls + " ✓ (Expected: 1)");
System.out.println(" Actual Cache Writes: " + cacheWrites + " ✓ (Expected: 1)");
System.out.println(" DB Calls Saved: " + (threadCount - 1) + " (Saved " + (threadCount - 1) * 10 + " seconds!)");
System.out.println(" Total Execution Time: " + duration + "ms");
System.out.println("\n🟢 SOLUTION BENEFITS:");
System.out.println(" 1. Single DB Fetch: Only " + dbCalls + " DB call (reduced from " + threadCount + " calls)");
System.out.println(" 2. Resource Optimized: Saved " + (threadCount - 1) + " redundant database fetches");
System.out.println(" 3. Cache Consistency: Only " + cacheWrites + " cache write (no race condition)");
System.out.println(" 4. Data Integrity: All threads see same data (single source of truth)");
System.out.println(" 5. Fine-Grained Locking: Only " + solutionCache.getActiveLockCount() + " lock(s) created");
System.out.println(" 6. Scalability: Other keys can be fetched in parallel (not demonstrated here)");
System.out.println("\n📝 DB Call Log:");
solutionCache.getDBCallLog().stream()
.forEach(log -> System.out.println(" • " + log));
System.out.println("\n📦 Final Cache State: " + solutionCache.getCacheState());
// Comparison
System.out.println("\n" + "═".repeat(100));
System.out.println("PROBLEM vs SOLUTION COMPARISON:");
System.out.println("═".repeat(100));
System.out.println(String.format("%-25s | %-25s | %-25s", "Metric", "Problem (No Lock)", "Solution (With Lock)"));
System.out.println("─".repeat(100));
System.out.println(String.format("%-25s | %-25s | %-25s", "DB Calls", "50 ❌", "1 ✓"));
System.out.println(String.format("%-25s | %-25s | %-25s", "Cache Writes", "50 ❌", "1 ✓"));
System.out.println(String.format("%-25s | %-25s | %-25s", "Execution Time", "~40 seconds", "~10 seconds"));
System.out.println(String.format("%-25s | %-25s | %-25s", "Performance Gain", "1x (baseline)", "4x faster ✓"));
System.out.println(String.format("%-25s | %-25s | %-25s", "Data Consistency", "Race condition ❌", "Guaranteed ✓"));
}
/**
* <b>simulateDBCall():</b>
* Simulates a database fetch operation with realistic latency.
* Different threads may get different versions if DB is being updated.
*
* <h4>Realistic Scenario:</h4>
* <ul>
* <li>Network latency to DB server: 5-20ms</li>
* <li>Query execution: 50-100ms</li>
* <li>Result serialization: 10-50ms</li>
* <li>Simulated total: 10 seconds (exaggerated for demo visibility)</li>
* </ul>
*
* @param key The employee ID being fetched.
* @return Database result: "DB_RECORD_[THREAD_NAME]"
* @throws InterruptedException If thread is interrupted during sleep.
*/
private static String simulateDBCall(String key) throws InterruptedException {
Thread.sleep(10000); // Simulate 10-second database query
return "DB_RECORD_" + Thread.currentThread().getName();
}
/**
* <b>logInfo():</b>
* Utility method for consistent logging across both problem and solution scenarios.
*
* @param key The cache key being accessed.
* @param message The event description.
*/
private static void logInfo(String key, String message) {
System.out.println(
String.format(
"[%-20s] [Key: %s] → %s",
Thread.currentThread().getName(),
key,
message
)
);
}
/**
* Architectural Observations (Extra Scenarios)
*
* 1) Same scenario using `synchronized` on an object (coarse-grained vs fine-grained)
*
* - If you place a single global `synchronized(globalLock)` around cache access,
* all cache operations (for every key) are serialized. This eliminates the
* thundering herd but destroys concurrency for unrelated keys.
*
* - Attempting per-key `synchronized` by doing `synchronized(key)` or
* `synchronized(key.intern())` has pitfalls:
* • `String.intern()` may cause unexpected retention and security/GC problems.
* • If `key` instances are mutable or come from untrusted sources, locking
* on them can be unsafe.
* • You still need a stable canonical lock object per key; use a dedicated
* lock map (see `CacheManagerWithPerKeyLocking`) rather than raw `synchronized`.
*
* - Summary: `synchronized` is simple, but avoid global monitors. Prefer fine-grained
* per-key locks (ReentrantLock or a lock-striping library) to keep unrelated keys
* parallel.
*
* 2) ReentrantLock behavior when different thread pools are involved
*
* - `ReentrantLock` is tied to the calling Thread instance. Reentrancy is only
* observed when the SAME Java Thread re-acquires the lock. If requests for the
* same logical operation are continued on a different thread (for example,
* work is submitted to another thread pool while the original thread releases),
* the lock does not transfer between threads.
*
* - Practical pitfalls when using locks across thread pools:
* • If a worker thread holds a lock and then submits a follow-up task to
* another pool that also expects that lock (or expects the first task to
* finish), you can deadlock or create long blocking chains.
* • Blocking threads in a limited thread-pool (e.g., servlet container or
* application worker pool) while waiting for locks reduces available
* throughput and can exhaust the pool.
*
* - Recommendations:
* • Do not hold locks while performing blocking I/O or while submitting
* work to other pools. Keep the locked section minimal (only the cache
* check + write).
* • Use `tryLock(timeout, unit)` or `lockInterruptibly()` to avoid permanent
* blocking and to allow interruption under shutdown.
* • Consider an asynchronous coalescing approach: instead of blocking threads,
* maintain an "in-flight" map of `CompletableFuture`/promises keyed by cache
* key. The first request creates and runs the load; subsequent requests
* attach to the same future and receive the result when ready. This avoids
* blocking pool threads.
* • Use lock-striping or libraries such as Guava's `Striped<Lock>` or Caffeine's
* built-in mechanisms to reduce memory overhead and manage lock lifecycles.
*
* 3) Memory and lifecycle considerations for per-key locks
*
* - A simple `ConcurrentHashMap<String, ReentrantLock>` can leak locks for keys
* that are rarely used. To avoid unbounded growth:
* • Use `WeakReference` or `WeakHashMap` variants for lock containers where
* appropriate (be careful with key types).
* • Periodically clean up unused locks after cache eviction or use a
* cache-backed lock store (e.g., Guava Cache with weak/expire entries).
*
* 4) Alternatives & production patterns
*
* - Request Coalescing (Singleflight): coalesce concurrent loads into a single
* in-progress promise (Java: `CompletableFuture`, libraries: Caffeine's
* `AsyncLoadingCache` or custom singleflight). This provides non-blocking
* scalability and avoids holding threads for the DB call.
* - Read-Through Caches: use battle-tested caches (Caffeine, Redis) with
* atomic loader semantics to avoid reinventing locking behavior.
* - Backpressure / Bulkheads: when thread pools differ, apply bulkhead
* patterns and bounded queues so that a lock-holder cannot starve critical
* resources.
*
* These observations are intentionally non-invasive: they document additional
* scenarios (synchronized object locking, and ReentrantLock interactions with
* thread pools) and outline architect-level mitigations without changing the
* existing demo behavior.
*/
@SuppressWarnings("unused")
private static void architectObservations() {
// Intentionally left non-invoked: see Javadoc above for details.
// Below are compact, illustrative code samples (examples only) that show
// how a `synchronized` variant and a non-blocking `CompletableFuture`
// coalescing approach can be implemented. These are NOT invoked by the
// demo; they are here purely for architecture-level reference.
// 1) Global synchronized monitor (coarse-grained) - BAD for unrelated keys
final Object globalMonitor = new Object();
final java.util.Map<String, String> syncCache = new java.util.concurrent.ConcurrentHashMap<>();
java.util.function.Function<String, String> synchronizedGlobal = (key) -> {
synchronized (globalMonitor) {
String v = syncCache.get(key);
if (v == null) {
// IMPORTANT: avoid long/blocking work while holding the monitor
v = "LOADED_SYNC_GLOBAL_" + key;
syncCache.put(key, v);
}
return v;
}
};
// 2) Per-key synchronized monitor using a monitor map (better, but watch GC)
final java.util.concurrent.ConcurrentHashMap<String, Object> monitors = new java.util.concurrent.ConcurrentHashMap<>();
java.util.function.Function<String, String> perKeySynchronized = (key) -> {
Object mon = monitors.computeIfAbsent(key, k -> new Object());
synchronized (mon) {
String v = syncCache.get(key);
if (v == null) {
v = "LOADED_SYNC_PERKEY_" + key;
syncCache.put(key, v);
}
return v;
}
};
// 3) Non-blocking request-coalescing using CompletableFuture (recommended)
final java.util.concurrent.ConcurrentHashMap<String, java.util.concurrent.CompletableFuture<String>> inFlight = new java.util.concurrent.ConcurrentHashMap<>();
final java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newCachedThreadPool();
java.util.function.Function<String, java.util.concurrent.CompletableFuture<String>> coalescingLoader = (key) -> {
return inFlight.computeIfAbsent(key, k -> java.util.concurrent.CompletableFuture.supplyAsync(() -> {
try {
// reuse demo's simulateDBCall for realistic latency (wrap checked exception)
return simulateDBCall(k);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}, executor).whenComplete((res, ex) -> inFlight.remove(k)));
};
// Example usage (not executed in demo):
java.util.concurrent.CompletableFuture<String> f = coalescingLoader.apply("EMP_ID_001");
String value = f.join();
System.out.println("value :"+value);
// Note: executor should be shut down in a real application lifecycle management.
}
}Output:
════════════════════════════════════════════════════════════════════════════════════════════════════
CACHE DATA FETCH PROBLEM & SOLUTION DEMONSTRATION
════════════════════════════════════════════════════════════════════════════════════════════════════
❌ PROBLEM: Uncontrolled Concurrent Cache Updates (Without Locking)
----------------------------------------------------------------------------------------------------
Scenario: 50 threads accessing SAME cache key: "EMP_ID_001"
Expected DB Calls: 1 | Expected Cache Writes: 1
Creating and starting 50 threads...
[ProblemThread-13 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-2 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-1 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-31 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-8 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-43 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-10 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-25 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-4 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-37 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-9 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-30 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-6 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-12 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-40 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-3 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-5 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-11 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-27 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-39 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-22 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-7 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-19 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-33 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-17 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-32 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-42 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-26 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-36 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-16 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-24 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-28 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-41 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-20 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-14 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-0 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-46 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-18 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-21 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-23 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-35 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-47 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-29 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-34 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-49 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-38 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-15 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-48 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-45 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-44 ] [Key: EMP_ID_001] → CACHE MISS - Making DB call...
[ProblemThread-13 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-13
[ProblemThread-4 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-4
[ProblemThread-30 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-30
[ProblemThread-1 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-1
[ProblemThread-6 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-6
[ProblemThread-10 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-10
[ProblemThread-37 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-37
[ProblemThread-10 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-10
[ProblemThread-47 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-47
[ProblemThread-8 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-8
[ProblemThread-47 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-47
[ProblemThread-8 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-8
[ProblemThread-20 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-20
[ProblemThread-21 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-21
[ProblemThread-42 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-42
[ProblemThread-33 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-33
[ProblemThread-0 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-0
[ProblemThread-27 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-27
[ProblemThread-3 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-3
[ProblemThread-0 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-0
[ProblemThread-3 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-3
[ProblemThread-15 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-15
[ProblemThread-48 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-48
[ProblemThread-15 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-15
[ProblemThread-48 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-48
[ProblemThread-44 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-44
[ProblemThread-44 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-44
[ProblemThread-5 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-5
[ProblemThread-38 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-38
[ProblemThread-38 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-38
[ProblemThread-25 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-25
[ProblemThread-45 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-45
[ProblemThread-29 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-29
[ProblemThread-5 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-5
[ProblemThread-34 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-34
[ProblemThread-49 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-49
[ProblemThread-34 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-34
[ProblemThread-49 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-49
[ProblemThread-11 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-11
[ProblemThread-27 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-27
[ProblemThread-11 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-11
[ProblemThread-39 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-39
[ProblemThread-22 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-22
[ProblemThread-33 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-33
[ProblemThread-42 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-42
[ProblemThread-21 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-21
[ProblemThread-36 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-36
[ProblemThread-17 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-17
[ProblemThread-36 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-36
[ProblemThread-16 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-16
[ProblemThread-28 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-28
[ProblemThread-7 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-7
[ProblemThread-39 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-39
[ProblemThread-19 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-19
[ProblemThread-32 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-32
[ProblemThread-26 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-26
[ProblemThread-32 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-32
[ProblemThread-41 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-41
[ProblemThread-14 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-14
[ProblemThread-41 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-41
[ProblemThread-20 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-20
[ProblemThread-24 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-24
[ProblemThread-18 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-18
[ProblemThread-35 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-35
[ProblemThread-9 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-9
[ProblemThread-23 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-23
[ProblemThread-46 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-46
[ProblemThread-37 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-37
[ProblemThread-43 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-43
[ProblemThread-40 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-40
[ProblemThread-6 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-6
[ProblemThread-30 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-30
[ProblemThread-1 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-1
[ProblemThread-31 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-31
[ProblemThread-4 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-4
[ProblemThread-31 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-31
[ProblemThread-2 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-2
[ProblemThread-12 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_ProblemThread-12
[ProblemThread-13 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-13
[ProblemThread-12 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-12
[ProblemThread-2 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-2
[ProblemThread-40 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-40
[ProblemThread-43 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-43
[ProblemThread-46 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-46
[ProblemThread-23 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-23
[ProblemThread-9 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-9
[ProblemThread-35 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-35
[ProblemThread-24 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-24
[ProblemThread-18 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-18
[ProblemThread-14 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-14
[ProblemThread-26 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-26
[ProblemThread-19 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-19
[ProblemThread-7 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-7
[ProblemThread-28 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-28
[ProblemThread-16 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-16
[ProblemThread-17 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-17
[ProblemThread-22 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-22
[ProblemThread-45 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-45
[ProblemThread-25 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-25
[ProblemThread-29 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_ProblemThread-29
────────────────────────────────────────────────────────────────────────────────────────────────────
PROBLEM ANALYSIS:
────────────────────────────────────────────────────────────────────────────────────────────────────
📊 METRICS:
Actual DB Calls: 50 ❌ (Expected: 1)
Actual Cache Writes: 50 ❌ (Expected: 1)
Redundant DB Calls: 49 (Wasted 490 seconds!)
Total Execution Time: 10046ms
🔴 PROBLEMS IDENTIFIED:
1. Thundering Herd: 50 threads made 50 DB calls for same data
2. Resource Waste: 49 unnecessary database fetches
3. Cache Thrashing: 50 cache updates (last write wins)
4. Data Consistency: Potential anomalies if DB was updated between calls
📝 DB Call Log:
• ProblemThread-13 fetched from DB
• ProblemThread-4 fetched from DB
• ProblemThread-1 fetched from DB
• ProblemThread-30 fetched from DB
• ProblemThread-6 fetched from DB
• ProblemThread-10 fetched from DB
• ProblemThread-37 fetched from DB
• ProblemThread-47 fetched from DB
• ProblemThread-8 fetched from DB
• ProblemThread-20 fetched from DB
• ProblemThread-21 fetched from DB
• ProblemThread-42 fetched from DB
• ProblemThread-33 fetched from DB
• ProblemThread-0 fetched from DB
• ProblemThread-27 fetched from DB
• ProblemThread-3 fetched from DB
• ProblemThread-15 fetched from DB
• ProblemThread-48 fetched from DB
• ProblemThread-44 fetched from DB
• ProblemThread-5 fetched from DB
• ProblemThread-38 fetched from DB
• ProblemThread-25 fetched from DB
• ProblemThread-45 fetched from DB
• ProblemThread-29 fetched from DB
• ProblemThread-34 fetched from DB
• ProblemThread-49 fetched from DB
• ProblemThread-11 fetched from DB
• ProblemThread-39 fetched from DB
• ProblemThread-22 fetched from DB
• ProblemThread-36 fetched from DB
• ProblemThread-17 fetched from DB
• ProblemThread-16 fetched from DB
• ProblemThread-28 fetched from DB
• ProblemThread-7 fetched from DB
• ProblemThread-19 fetched from DB
• ProblemThread-32 fetched from DB
• ProblemThread-26 fetched from DB
• ProblemThread-41 fetched from DB
• ProblemThread-14 fetched from DB
• ProblemThread-24 fetched from DB
• ProblemThread-18 fetched from DB
• ProblemThread-35 fetched from DB
• ProblemThread-9 fetched from DB
• ProblemThread-23 fetched from DB
• ProblemThread-46 fetched from DB
• ProblemThread-43 fetched from DB
• ProblemThread-40 fetched from DB
• ProblemThread-31 fetched from DB
• ProblemThread-2 fetched from DB
• ProblemThread-12 fetched from DB
📦 Final Cache State: {EMP_ID_001=DB_RECORD_ProblemThread-12}
════════════════════════════════════════════════════════════════════════════════════════════════════
✓ SOLUTION: Per-Key ReentrantLock (With Locking)
----------------------------------------------------------------------------------------------------
Scenario: 50 threads accessing SAME cache key: "EMP_ID_001"
With Per-Key ReentrantLock implementation
Expected DB Calls: 1 | Expected Cache Writes: 1
Creating and starting 50 threads...
[SolutionThread-0 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-1 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-2 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-1 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-3 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-1 ] [Key: EMP_ID_001] → DB call starting (only thread with this key lock)...
[SolutionThread-4 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-6 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-5 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-7 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-8 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-9 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-12 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-13 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-10 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-14 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-11 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-15 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-17 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-16 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-18 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-19 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-20 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-21 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-22 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-23 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-24 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-25 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-27 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-26 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-28 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-30 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-29 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-31 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-32 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-33 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-34 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-35 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-37 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-36 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-38 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-39 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-40 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-41 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-42 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-43 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-44 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-45 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-46 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-47 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-48 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-49 ] [Key: EMP_ID_001] → CACHE MISS - Acquiring per-key lock...
[SolutionThread-1 ] [Key: EMP_ID_001] → DB returned: DB_RECORD_SolutionThread-1
[SolutionThread-1 ] [Key: EMP_ID_001] → Cache updated with: DB_RECORD_SolutionThread-1
[SolutionThread-1 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-6 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-6 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-6 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-4 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-4 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-4 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-3 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-3 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-3 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-2 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-2 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-2 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-5 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-5 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-5 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-0 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-0 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-0 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-7 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-7 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-7 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-8 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-8 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-8 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-9 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-9 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-9 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-12 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-12 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-12 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-13 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-13 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-13 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-10 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-10 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-10 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-14 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-14 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-14 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-11 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-11 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-11 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-15 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-15 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-15 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-17 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-17 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-17 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-16 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-16 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-16 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-18 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-18 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-18 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-19 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-19 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-19 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-20 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-20 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-20 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-21 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-21 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-21 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-22 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-22 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-22 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-23 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-23 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-23 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-24 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-24 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-24 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-25 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-25 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-25 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-27 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-27 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-27 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-26 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-26 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-26 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-28 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-28 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-28 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-30 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-30 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-30 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-29 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-29 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-29 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-31 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-31 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-31 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-32 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-32 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-32 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-33 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-33 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-33 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-34 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-34 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-34 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-35 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-35 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-35 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-37 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-37 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-37 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-36 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-36 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-36 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-38 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-38 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-38 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-39 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-39 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-39 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-40 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-40 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-40 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-41 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-41 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-41 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-42 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-42 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-42 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-43 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-43 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-43 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-44 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-44 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-44 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-45 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-45 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-45 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-46 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-46 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-46 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-47 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-47 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-47 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-48 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-48 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-48 ] [Key: EMP_ID_001] → Releasing per-key lock...
[SolutionThread-49 ] [Key: EMP_ID_001] → Lock acquired - checking cache again...
[SolutionThread-49 ] [Key: EMP_ID_001] → DATA FOUND (Another thread already fetched): DB_RECORD_SolutionThread-1
[SolutionThread-49 ] [Key: EMP_ID_001] → Releasing per-key lock...
────────────────────────────────────────────────────────────────────────────────────────────────────
SOLUTION ANALYSIS:
────────────────────────────────────────────────────────────────────────────────────────────────────
📊 METRICS:
Actual DB Calls: 1 ✓ (Expected: 1)
Actual Cache Writes: 1 ✓ (Expected: 1)
DB Calls Saved: 49 (Saved 490 seconds!)
Total Execution Time: 10049ms
🟢 SOLUTION BENEFITS:
1. Single DB Fetch: Only 1 DB call (reduced from 50 calls)
2. Resource Optimized: Saved 49 redundant database fetches
3. Cache Consistency: Only 1 cache write (no race condition)
4. Data Integrity: All threads see same data (single source of truth)
5. Fine-Grained Locking: Only 1 lock(s) created
6. Scalability: Other keys can be fetched in parallel (not demonstrated here)
📝 DB Call Log:
• SolutionThread-1 fetched from DB
📦 Final Cache State: {EMP_ID_001=DB_RECORD_SolutionThread-1}
════════════════════════════════════════════════════════════════════════════════════════════════════
PROBLEM vs SOLUTION COMPARISON:
════════════════════════════════════════════════════════════════════════════════════════════════════
Metric | Problem (No Lock) | Solution (With Lock)
────────────────────────────────────────────────────────────────────────────────────────────────────
DB Calls | 50 ❌ | 1 ✓
Cache Writes | 50 ❌ | 1 ✓
Execution Time | ~40 seconds | ~10 seconds
Performance Gain | 1x (baseline) | 4x faster ✓
Data Consistency | Race condition ❌ | Guaranteed ✓
════════════════════════════════════════════════════════════════════════════════════════════════════
DEMONSTRATION COMPLETED
════════════════════════════════════════════════════════════════════════════════════════════════════
════════════════════════════════════════════════════════════════════════════════════════════════════
value :DB_RECORD_pool-1-thread-1
Process finished with exit code 0
So:
-
synchronized(this)→ one lock per object - One ReentrantLock → one lock for all callers
- Lock per ThreadGroup → possible, but usually not ideal for web apps. In a web application we usually not follow:
Thread → ThreadGroup → lock -
Lock per cache key → best fit for your original DB/cache problem. chatgpt ref In a web application, you normally care about:
request → key → cache → DB
For your DB cache scenario
private static final ReentrantLock lock = new ReentrantLock();
private static String getData(String key) {
String data = mapCache.get(key);
// Cache hit
if (data != null) return data;
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// Double-check
data = mapCache.get(key);
if (data != null) return data;
// Only one thread reaches DB
System.out.println(Thread.currentThread().getName() + " -> DB CALL" );
data = fetchFromDB(key);
mapCache.put(key, data);
return data;
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName()+ " -> LOCK TIMEOUT");
// Handle timeout
return null;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}Interface ReadWriteLock - Class ReentrantReadWriteLock
OpenJDK Downloads : In a class-based object-oriented language, in general, state is carried by instances, methods are carried by classes, and inheritance is only of structure and behavior. Basic, Refactoring Techniques
Method signature: It consists of method name and parameter list (number/type/order of the parameters). methodName(parametersList y). An instance method in a subclass with the same signature and return type as an instance method in the super-class overrides the super-class's method.
Java OOP concepts
Class - Collection of a common features of a group of object [static/instance Fields, blocks and Methods]
Object - Instance of a class (instance fields)
Abstraction - Process of hiding complex info and providing required info like API, Marker Interfaces ...
Encapsulation(Security) - Class Binding up with data members(fields) and member functions.
Inheritance (Reusability by placing common code in single class)
1. Multilevel - {A -> B -> C} 2. Multiple - Diamond problem {A <- (B) -> C} [Java not supports] 3. Cyclic {A <-> B} [Java not supports]
* Is-A Relation - Class A extends B
* Hash-A Relation - Class A { B obj = new B(); } - (Composition/Aggregation)
Polymorphism (Flexibility) 1. Compile-Time Overloading 2. Runtime Overriding [Greek - "many forms"]
int[] arr = {1,2,3}; int arrLength = arr.length; // Fixed length of sequential blocks to hold same data type
String str = "Yash"; int strLength = str.length(); // Immutable Object value can't be changed.
List<?> collections = new ArrayList<String>(); int collectionGroupSize = collections.size();
Map<?, ?> mapEntry = new HashMap<String, String>();
Set<?> keySet = mapEntry.keySet(); // Set of Key's
Set<?> entrySet = mapEntry.entrySet(); // Set of Entries [Key, Value]
// Immutable Objects once created they can't be modified. final class Integer/String/Employee
Integer val = Integer.valueOf("100"); String str2 = String.valueOf(100); // Immutable classes
final class Employee { // All Wrapper classes, java.util.UUID, java.io.File ...
private final String empName; // Field as Final(values can be assigned only once) Only getter functions.
public Employee(String name) { this.empName = name; }
} Native Java Code for Hashtable.h, Hashtable.cpp
SQL API.
You can check your current JDK and JRE versions on your command prompt respectively,
- JDK
javac -version [C:\Program Files\Java\jdk1.8.0_121\bin]o/p:javac 1.8.0_121 - JRE
java -version[C:\Program Files\Java\jdk1.8.0_121\bin]o/P:java version "1.8.0_102"
JAVA_HOME - Must be set to JDK otherwise maven projects leads to compilation error. [ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK? C:\Softwares\OpenJDK\, 7-zip
Fatal error compiling: invalid target release: JRE and JDK must be of same version
1.8.0.XXX
Disable TLS 1.0 and 1.1
security-libs/javax.net.ssl: TLS 1.0 and 1.1 are versions of the TLS protocol that are no longer considered secure and have been superseded by more secure and modern versions (TLS 1.2 and 1.3).
Core Java
-
Java Programming Language Basics
- Object, Class, Encapsulation, Interface, Inheritance, Polymorphism (Method Overloading, Overriding)
- JVM Architecture, Memory Areas
- JVM Class Loader SubSystem
- Core Java Interview Questions & Programs
- Interview Concepts
Stack Posts
- Comparable vs Comparator
- Collections and Arrays
-
String, StringBuffer, and StringBuilder
- String reverse
- Remove single char
- File data to String
- Unicode equality check Spacing entities
- split(String regex, int limit)
- Longest String of an array
-
Object Serialization
- Interface's Serializable vs Externalizable
- Transient Keyword
-
implements Runnablevsextends Thread - JSON
- Files,
Logging API- Append text to Existing file
- Counting number of words in a file
- Properties
- Properties with reference key