# Java Concurrency: Atomic Variables & Locks

### 1. Atomic Variables
Atomic variables are tools that let us update a value safely when many threads are running. They ensure that an action (like adding 1) happens **all at once** without being interrupted.

*   **Difference:** Normal variables can lose data if two threads change them at the exact same time. Atomic variables use a "try-and-retry" method (CAS) to stay accurate without stopping other threads.

### 2. Common Atomic Classes
*   `AtomicInteger`
*   `AtomicLong`
*   `AtomicBoolean`
*   `AtomicReference`

**Use Case:** We use `AtomicInteger` for a **hit counter** on a website. It keeps the total count correct even if thousands of users click a button at the same second.

### 3. Locks vs. Atomic Variables

| Feature | Atomic Variables | Locks (`synchronized`) |
| :--- | :--- | :--- |
| **Speed** | Very Fast | Slower |
| **Thread Behavior** | Keep running (no waiting) | Stop and wait their turn |
| **Best For** | Single numbers or flags | Groups of variables or big tasks |

*   **We use Atomic Variables when:** We only need to change one value quickly and want to avoid the slowdown of locking.
*   **We use Locks when:** We need to update **multiple related values** together or when the work takes a long time (like saving a file).

### 4. Poor Performance without Race Conditions
Even if our code is "correct" (no data is lost), it can still be slow if many threads fight for the same resources. This is called **high contention**.

**Factors that limit scaling:**
1.  **Lock Contention:** Many threads wait in a long line for one lock, so only one thread actually works at a time.
2.  **Context Switching:** The CPU spends more time "swapping" threads in and out than actually running our code.
3.  **Memory Bottlenecks:** Threads might be waiting for data to move between the main memory and the CPU.

### 5. Why More Threads Can Slow Us Down
Adding more threads has a "cost" that eventually outweighs the benefits.

-   **Context Switching:** Every time the CPU moves from one thread to another, it has to save and load data. This wastes time.
-   **Contention:** If we have 100 threads but only 1 resource, 99 threads are sitting idle, which wastes memory.
-   **Cache Coherence:** When one thread changes data, the CPU must tell all other cores to update their private "caches." This constant communication slows down the whole system.
-   **Synchronization Overhead:** The tools we use to stay safe (like locks or atomic signals) require extra CPU work to manage.

### 6. Deadlocks in Production vs. Testing
Deadlocks depend on **timing**. In testing, threads might always run in a "safe" order because the computer is less busy. In production, unexpected delays or high traffic can cause threads to grab locks in the "wrong" order, causing a freeze.

**Strategies to find deadlocks during testing:**
1.  **Thread Fuzzing:** We can add random, tiny delays (like `Thread.sleep()`) in our code during testing. This forces different timings and helps expose hidden deadlocks.
2.  **Stress Testing:** We can run the program with a much higher number of threads and data than we expect. This makes it more likely that the rare "wrong timing" will happen.