## Question 1


1 - What are atomic variables?

Atomic variables are variables that provide thread-safe operations without using explicit locks. Operations such as incrementing or updating a value are performed atomically, meaning they cannot be interrupted by other threads.

Ordinary variables do not provide this guarantee. For example, the expression `counter++` consists of multiple steps, and different threads may interfere with each other, causing race conditions.

Atomic variables are mainly used to safely share data between multiple threads.

## Question 2

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

Some classes from the `java.util.concurrent.atomic` package are:

- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicReference

A common use case for `AtomicInteger` is a shared counter. Multiple threads can safely increment the counter without using locks.

## Question 3

3 - Compare locks with atomic variables.

Atomic variables are usually faster and simpler for operations on a single variable. They have lower overhead and do not require explicit locking.

Locks are better when multiple operations or multiple shared variables must be protected together. They provide more flexibility but usually have higher overhead.

Use atomic variables for simple updates such as counters. Use locks for complex critical sections that involve several operations.

## Question 4

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

A program can be free of race conditions and still perform poorly because correctness does not guarantee scalability.

Some factors that limit performance are:

1. Lock contention, where many threads wait for the same lock.
2. Thread blocking, which reduces parallel execution.
3. Synchronization overhead, which adds extra work for coordinating threads.

As a result, the program remains correct but may not scale well under heavy load.

## Question 5

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

Adding more threads does not always improve performance.

- Context switching takes CPU time when the operating system switches between threads.
- Contention happens when many threads compete for the same resources.
- Cache coherence creates extra work for processors to keep shared data consistent.
- Synchronization overhead increases because locks and coordination mechanisms require additional processing.

Because of these costs, too many threads can actually reduce performance.

## Question 6

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

Deadlocks often depend on specific thread schedules. During testing, the required scheduling order may never occur. In production, higher load and different timing make deadlocks more likely.

Two ways to expose deadlocks during testing are:

1. Stress testing with many threads and repeated executions.
2. Adding random delays to create different thread interleavings.

These techniques increase the chance of reproducing deadlock situations before deployment.