Flashcards for topic Concurrency
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 critical problem can occur when threads share mutable data without proper synchronization, even if the operations are atomic?
Without synchronization, one thread's changes might not be visible to other threads. Even if operations are atomically readable/writable, synchronization is required for:
This can lead to:
Example: A boolean flag used for thread termination may never be seen by the thread meant to stop, causing an infinite loop.
Why is declaring a variable as volatile
insufficient for operations like incrementing a counter across multiple threads?
volatile
only guarantees visibility of the most recently written value, but does not provide atomicity for compound operations.
When incrementing (counter++
):
Example problem:
private static volatile int counter = 0; // BROKEN public static int incrementAndGet() { return counter++; // NOT ATOMIC! Read and write are separate operations }
Two threads could both read 0
, increment to 1
, and both write 1
, causing one increment to be lost.
Proper solutions:
synchronized
methods/blocksAtomicInteger
, AtomicLong
, etc.private static final AtomicInteger counter = new AtomicInteger(0); public static int incrementAndGet() { return counter.getAndIncrement(); // Atomic operation }
What is the "effectively immutable" pattern in concurrent programming and why is it useful?
Effectively immutable objects:
Benefits:
Implementation pattern:
this
reference during constructionExample:
public class EffectivelyImmutable { private final List<String> data; // Note: List itself is mutable public EffectivelyImmutable(Collection<String> initial) { // Make defensive copy to ensure encapsulation this.data = new ArrayList<>(initial); } // Only provide non-modifying access methods public boolean contains(String s) { return data.contains(s); } public int size() { return data.size(); } // No methods that would modify data }
Once safely published, this object can be freely shared across threads without additional synchronization.
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:
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 }
What are the implications of using System.nanoTime() vs System.currentTimeMillis() for interval timing, and why is this distinction important in benchmarking?
System.nanoTime() vs System.currentTimeMillis() for interval timing:
| System.nanoTime() | System.currentTimeMillis() | |-------------------|----------------------------| | Measures elapsed time (monotonic clock) | Measures wall-clock time | | Not affected by system clock changes | Affected by time zone changes, NTP adjustments, daylight savings | | Higher precision (nanosecond) | Lower precision (millisecond) | | Only meaningful for interval measurements | Can be used for timestamps and intervals | | Cannot be used to determine actual time of day | Reflects actual time of day |
Implications for benchmarking:
Practical consequences:
Best practice:
// Correct timing approach long startTime = System.nanoTime(); performOperation(); long elapsedNanos = System.nanoTime() - startTime;
For serious benchmarking, specialized frameworks like JMH (Java Microbenchmark Harness) should be used to account for JIT compilation, warm-up effects, and other complexities.
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 is the key requirement for a robust, responsive, portable concurrent program with respect to runnable threads?
The key requirement is: ensure that the average number of runnable threads is not significantly greater than the number of processors.
When this requirement is met:
Practical implementation techniques:
Note that runnable threads ≠ total threads. Blocked/waiting threads don't count toward this limit.
What is the double-check idiom for lazy initialization? Explain its implementation, the critical volatile requirement, and how the local variable optimization improves performance.
The double-check idiom is a thread-safe pattern for lazy initialization that minimizes synchronization overhead:
private volatile FieldType field; private FieldType getField() { FieldType result = field; // Local variable optimization if (result == null) { // First check (no locking) synchronized(this) { result = field; // Re-read after acquiring lock if (field == null) // Second check (with locking) field = result = computeFieldValue(); } } return result; }
result
Used when lazy initialization is needed for performance on instance fields with high initialization cost but infrequent access.
Showing 10 of 48 cards. Add this deck to your collection to see all cards.