A deadlock is a state in which concurrent execution stops because threads wait indefinitely for shared resources or locks held by one another. The application becomes unresponsive and may ultimately crash. section 7.7.1
Consider a concurrent cache protected by a write lock. A thread must release that lock even when an operation fails; otherwise, waiting threads remain blocked. The reliable pattern is to acquire the lock, perform the operation inside try, and unlock in finally. section 7.7.3
Nested cache operations
Suppose set acquires a write lock and calls evictOneEntry. If evictOneEntry also tries to acquire a non-reentrant write lock, the same thread waits for a lock it already holds, while set waits for evictOneEntry to finish. A reentrant lock lets the same thread acquire the held lock again, allowing eviction to continue and avoiding this particular deadlock. section 7.7.4
Reduce deadlock risk by:
- Releasing locks in a finally block, including when protected operations fail. section 7.7.3
- Using synchronization mechanisms carefully and avoiding unnecessary concurrent internal structures that can introduce additional locking interactions. section 7.7.1
- Considering reentrant locks when synchronized methods can call other synchronized methods on the same resource. section 7.7.4
Listing 7.10 Java concurrent LFU cache set
public boolean set(Key key, Value value) {
writeLock.lock(); #1
try { #2
if (this.hashTable.contains(key)) {
PriorityQueueNode node = this.hashTable.get(key);
node.setValue(value);
return node.updatePriority(node.getCounter());
} else if (this.getSize() >= this.maxSize) {
this.evictOneEntry(); #3
}
PriorityQueueNode newNode = this.elements.add(key, value, 1);
this.hashTable.put(key, newNode);
return true;
} finally { #4
writeLock.unlock(); #5
}
}
Inspect the concurrent LFU cache set method to see lock acquisition, protected mutation, eviction, and guaranteed unlocking in executable order. section 7.7.3














