Files
2026-07-03 22:42:53 +03:30

3.5 KiB

⚛️ Atomic Variables & Synchronization

1. What are atomic variables?

Atomic variables are essentially variables that allow us to perform thread-safe operations without explicitly using locks (like synchronized). When I use an atomic variable, the operation (like read-modify-write) is done in a single, indivisible hardware step (using CAS - Compare-And-Swap). This is different from ordinary variables where an operation like count++ actually takes three steps (read, increment, write) and can get interrupted by other threads, leading to race conditions.

2. Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types AtomicInteger AtomicLong AtomicBoolean AtomicReference

Use case: I usually use AtomicInteger when I need a simple counter in a web server or an application to keep track of concurrent requests or active users. It's much faster than wrapping an int inside a synchronized block.

3. Compare locks with atomic variables.

  • Atomic variables are a better choice when I only need to update a single variable or flag independently (like a counter). They have less overhead because they don't block threads.
  • Locks are necessary when my logic involves updating multiple variables at the same time that depend on each other, or when I need to protect a complex critical section of code.

🔒 Locks & Concurrent Design

4. A program is completely free of race conditions but still performs poorly under high contention.

Even if my code has zero race conditions, it can still run slowly due to:

  1. High Contention: If all my threads are constantly trying to acquire the same lock, most of them will just be waiting in a queue doing nothing.
  2. Coarse-grained locking: If I lock an entire large method instead of just the critical section, I limit concurrency unnecessarily.
  3. Context Switching Overhead: The OS wastes a lot of CPU cycles switching between threads that are constantly pausing and waking up to check locks.

5. Many concurrent systems experience performance degradation as the number of threads increases.

Adding threads doesn't scale linearly. The main reasons are:

  • Context switching: The CPU spends too much time saving and loading thread states instead of running my actual code.
  • Contention & Synchronization overhead: More threads mean more competition for the same locks. Managing these locks takes time.
  • Cache coherence: Threads on different CPU cores modify shared data, forcing the CPU to constantly update and synchronize caches across cores, which slows down the memory bus.

⚠️ Deadlocks

6. Deadlocks often only appear in production, not during testing. During local testing, my computer usually runs threads fast and with low load, so they often execute sequentially and never hit that exact timing needed for a deadlock. In production, thousands of users hit the system concurrently with unpredictable network delays, creating the perfect random interleaving of threads that causes a deadlock cycle. Strategies to expose deadlocks:

  1. Stress/Load Testing: I can write a test that spawns hundreds of threads simulating simultaneous high-volume transactions to increase lock contention.
  2. Strategic Thread.sleep(): I can add Thread.sleep(10) or Thread.yield() right after a thread acquires its first lock but before it gets the second one. This forces a context switch and drastically increases the chance of catching a cyclic deadlock.