###  Atomic Variables & Synchronization

---

**1 -** 

These kinds of variables make single variables lock-free and safe to use multiple threads.

Atomic variables solve the problem of thread-safe updates to shared variables without using explicit synchronization.

Ordinary ones may not be seen by other threads but atomic  variables are immediately visible to other threads. Threads can interleave between operations like increment and cause Race Condition.

**2 -** 

`AtomicInteger`, `AtomicLong`, `AtomicBoolean`, `AtomicIntegerArray`

For example, the most common use case for `AtomicBoolean` is ensuring that a particular action or initialization routine executes exactly once in a multi-threaded environment, even when multiple threads attempt to trigger it simultaneously.

**3 -** 

An atomic variable is better for one variable, one operation and Locks are better in multiple variables or multiple steps:

Single counter increment >>> Atomic variable

Long-running update operation >>> Lock

###  Locks & Concurrent Design

---

**4 -**

Under high contention, even correctly synchronized code can degrade severely due to how threads coordinate access to shared resources.

Three concurrency-related factors that can be mentioned are: Lock Contention (Serialization), Cache Coherency Traffic, Oversubscription and Context Switching

**5 -**

Adding more threads increases context switching overhead as the OS spends more time saving and restoring thread states than doing actual work. Higher contention for shared resources (locks, atomic variables) causes threads to serialize and wait, while cache coherence traffic forces CPUs to constantly invalidate and reload shared data. These factors create a point of diminishing returns where additional threads reduce throughput instead of improving it.

### Deadlocks

---

**6 -**

Deadlocks are rare timing-dependent bugs. Testing environments are too controlled (fewer threads, slower execution, predictable scheduling) to trigger the precise lock interleaving required. Production's chaos—more threads, faster speeds, random OS scheduling—makes that rare timing much more likely to happen.

Strategy 1: Use a controlled randomizer for thread scheduling. Inject small, random delays (`Thread.sleep()` or `yield()`) at strategic points between lock acquisitions.

Strategy 2: Run tests with aggressive oversubscription. Test with significantly more threads than CPU cores.