1 - What are atomic variables?

Atomic variables in Java are thread-safe variables that perform operations like read, write, and increment atomically. Their purpose is to safely share data between threads without locks for simple operations. Unlike ordinary variables, atomic variables prevent race conditions during simultaneous access.

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

AtomicInteger is commonly used as a thread-safe counter, for example to count how many requests or tasks have been processed by multiple threads.
AtomicLong
AtomicBoolean
AtomicReference

3 - Compare locks with atomic variables.

Atomic variables are best for simple, single-variable operations such as incrementing a counter or updating a flag. They are usually faster and lighter.
Locks are better when a program needs to protect multiple operations or multiple variables as one critical section. A lock ensures that only one thread can execute that block of code at a time, which is useful for more complex logic.

Use atomic variables for simple thread-safe operations on a single value.
Use locks when you need to protect more complex shared state or a larger block of code.

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

Threads block waiting for locks, turning parallel execution into sequential processing. CPU time is wasted managing queues rather than computing.
Frequent blocking/unblocking forces the OS to save/restore thread states. High switch rates consume CPU cycles needed for actual work.
Unrelated variables in the same cache line cause unnecessary cache invalidations across cores, forcing slow main memory accesses despite logical independence.

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

Adding more threads does not always improve performance because after a certain point, the extra threads create more overhead.
Context switching: if there are more runnable threads than CPU cores, the OS must frequently pause one thread and run another. This switching costs time and can reduce overall throughput.
Contention: many threads may compete for the same shared resources, such as locks, atomic variables, memory, or I/O. As contention increases, threads spend more time waiting than working.
Cache coherence: when multiple cores repeatedly read and write shared data, the CPU must keep their caches consistent. This creates extra communication between cores and slows memory access.
Synchronization overhead: locks, mutexes, barriers, and atomic operations all add coordination costs. Even when they guarantee correctness, they can reduce scalability under heavy load.

6 - Deadlocks often only appear in production, not during testing.

Deadlocks may appear in production but not during testing because thread scheduling is non-deterministic. A deadlock happens only when threads acquire locks in a particular timing and order. During testing, that exact interleaving may never occur, but in production different timing, higher load, more threads, and different hardware can make it happen.
Two ways to make deadlocks more likely during testing are:
Increase concurrency and repetition
Run the program many times with more threads, heavier load, and stress tests. More executions create more possible thread interleavings, which increases the chance of reaching the deadlock situation.
Add artificial delays around lock operations