3.6 KiB
3.6 KiB
1. Atomic Variables & Synchronization
What are atomic variables and what are they used for?
- Atomic variables are specialized variables that handle updates in a single, uninterruptible step. They allow multiple threads to work on the same data safely without needing to use manual locks.
Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types, and briefly describe a typical use case for one of them.
- Four common classes from the package are:
AtomicInteger (for int values)
AtomicLong (for long values)
AtomicBoolean (for boolean values)
AtomicReference (for object references)
For example, AtomicInteger is usually used for high-performance counters that need to be updated by multiple threads simultaneously.
Compare locks with atomic variables. Explain where each one is usually used.
- Atomic variables are usually used when we just need to update a single variable or a flag since they're faster and simpler. But locks are usually used when dealing with multiple dependent variables or complex logic and conditions.
2. Locks & Concurrent Design
Explain why a program could be completely free of race conditions but still perform poorly under high contention. Discuss at least three concurrency-related factors that may limit scalability even when correctness is guaranteed.
- Correctness doesn't always mean scalability.
- Coarse-Grained Locking: Locking a large section of code instead of just the piece you need.
- Lock Contention: Having too many threads fighting to grab the exact same lock at the exact same time.
- Thread Orchestration Overhead: The Operating System using significant CPU power just to "manage" threads (switching, waking, and pausing) rather than actually running the code.
Explain why adding more threads does not always improve performance. Discuss what concepts like Context switching, Contention, Cache coherence, and Synchronization overhead mean.
- Having too many threads does not necessarily mean better performance.
- Context switching: Changing from thread A to thread B requires saving and loading thread states, Which spends CPU power.
- Contention: More threads mean more competition for limited resources, which leads to threads spending their time waiting for locks to release instead of processing data.
- Cache coherence: When cores share data, they have to constantly sync their local caches. This interaction between CPU cores slows everything down.
- Synchronization overhead: Acquiring and releasing locks require extra CPU instructions. If the code uses too many locks, the CPU spends all its time checking safety protocols instead of doing the actual work.
3. Deadlocks
Explain why Deadlocks often only appear in production, not during testing from a thread-scheduling perspective.
- Deadlocks are timing-dependent. They only occur when threads interleave in a very specific, rare order that often doesn’t happen under light testing loads.
Describe two strategies a developer can use to increase the likelihood of exposing deadlocks during testing.
- We can use some strategies, such as:
- High-Concurrency Stress Testing: Using a large number of threads performing random operations simultaneously to trigger high-concurrency scenarios that might not happen with just 2–3 threads.
- Injecting Artificial Timing Delays: Adding a random
Thread.sleep(10)before acquiring and releasing locks or during other high stress moments. This forces the threads to encounter non-deterministic execution paths which makes timing-based bugs much easier to spot.