Flashcards for topic Concurrency
What solution pattern should be used to prevent ConcurrentModificationException and deadlocks when notifying observers, and how is it implemented?
Solution: The "snapshot" pattern
Implementation:
private void notifyElementAdded(E element) { // Create a snapshot copy outside of synchronized block List<SetObserver<E>> snapshot = null; synchronized(observers) { snapshot = new ArrayList<>(observers); } // Iterate over the snapshot without holding the lock for (SetObserver<E> observer : snapshot) observer.added(this, element); }
This pattern:
What is an "open call" in concurrent programming, and why should you prefer it?
An open call is a method invocation made outside of a synchronized region.
Benefits:
Implementation pattern:
This is a fundamental principle for achieving high-performance thread-safe code.
What does "safe publication" mean in concurrent programming and what are the ways to achieve it?
Safe publication refers to safely making an object visible to other threads in a way that guarantees the object's state is fully initialized and visible.
Objects shared without safe publication might appear partially constructed to other threads.
Ways to safely publish an object:
Safe publication is especially important for objects that will be effectively immutable (constructed, then never modified again) but aren't technically immutable classes.
Example:
// Safe publication using volatile private static volatile SafeObject instance; public static SafeObject getInstance() { if (instance == null) { synchronized(SafeObject.class) { if (instance == null) { instance = new SafeObject(); // Safely published via volatile } } } return instance; }
Describe the Lazy Initialization Holder Class idiom. When is it appropriate to use, and why is it considered efficient?
The Lazy Initialization Holder Class idiom is a thread-safe pattern for initializing static fields:
// Lazy initialization holder class idiom for static fields private static class FieldHolder { static final FieldType field = computeFieldValue(); } private static FieldType getField() { return FieldHolder.field; }
When to use:
Why it's efficient:
This idiom is considered the most elegant approach for lazy initialization of static fields as it combines thread safety with minimal performance impact.
What are the five levels of thread safety for classes? For each level, explain its meaning and provide an example.
The five levels of thread safety:
Immutable
Unconditionally thread-safe
Conditionally thread-safe
Not thread-safe
Thread-hostile
Understanding these levels is crucial for properly documenting a class's thread safety properties and for clients to use the class correctly.
What is the standard idiom for using the wait method, and why must wait always be used inside a loop? Explain the consequences of not following this pattern.
Standard wait method idiom:
// The standard idiom for using the wait method synchronized (obj) { while (<condition does not hold>) obj.wait(); // Releases lock, and reacquires on wakeup // Perform action appropriate to condition }
Why wait must be used in a loop:
Testing before waiting (protects liveness):
Testing after waiting (protects safety):
Consequences of not using a loop:
Reasons a thread might wake when condition doesn't hold:
When implementing concurrent timing with CountDownLatch, what potential thread starvation issue must be considered, and what causes it?
When implementing concurrent timing with CountDownLatch, you must be aware of thread starvation deadlock:
The issue:
Specific deadlock scenario:
Example:
// This will deadlock if concurrency > executor's max threads public static long time(Executor executor, int concurrency, Runnable action) { CountDownLatch ready = new CountDownLatch(concurrency); // Additional code... ready.await(); // Deadlocks if not enough threads to count down // ... }
Prevention:
Compare and contrast the use of notify vs. notifyAll. When is it safe to use notify instead of notifyAll, and what risks does this optimization introduce?
notify vs. notifyAll:
| notify | notifyAll | |--------|-----------| | Wakes a single waiting thread | Wakes all waiting threads | | More efficient (fewer thread wakeups) | Less efficient (unnecessary wakeups) | | Risk of leaving intended recipients waiting | Guaranteed to wake all necessary threads | | Requires careful reasoning about wait-sets | Conservative and always correct |
When it's safe to use notify (optimization conditions):
Risks introduced by using notify:
Best Practice: Use notifyAll by default as a safer approach. Only use notify as a deliberate optimization after careful analysis of all possible waiter threads and their conditions.
Example of notify risk:
// Thread A and B wait on condition X // Thread C waits on condition Y // All wait on the same object synchronized(obj) { // If we notify() when X becomes true // Thread C might wake instead of A or B // A and B might never wake }
How does the primitive field handling differ in lazy initialization patterns compared to reference fields?
For primitive fields in lazy initialization:
The null check becomes a comparison against 0 (default value for numerical primitives)
// Instead of checking against null: if (numericField == 0) // For primitives
For primitives other than long/double that can tolerate repeated initialization, you can use the racy single-check idiom (removing volatile)
Primitive initialization must handle the ambiguity that the default value (0) might be a valid computed value, unlike reference fields where null clearly indicates uninitialized state
Atomic operations and memory barriers work differently for primitives vs. references, requiring special attention to memory consistency effects
This creates additional complexity when adapting reference-based idioms to primitive fields.
What makes thread priorities problematic for solving concurrency issues, and what is their appropriate use case?
Problems with thread priorities:
Incorrect use:
Appropriate use (limited):
Thread priorities should be considered hints to the scheduler, not guarantees of execution order or timing.
Showing 10 of 48 cards. Add this deck to your collection to see all cards.