# Answers

## Q1
Atomic variable is a type of variable that combines all three process of: read, write and update into a single operation.  
as an example when we write `i++` it may seem like a single operation  
but the compiler needs to read `i`(read), add 1 to it (update) and change `i` to new value (write)  
since all these operations are combined into one operation race conditions doesn't occur  
unlike normal variables.

## Q2
`AtomicInteeger`, `AtomicBoolean`.`AtomicLong` & `AtomicReference`

#### `AtomicInteeger` Use case
used as counters that are shared across threads
#### `AtomicBoolean` Use case
used as flags that are used across threads (like `running` flag)
#### `AtomicLong` Use case
in general anything that needs long operations for multiple threads such as a bank account balance or an id counter
#### `AtomicReference` Use case
it's used whenever you need atomic operations on any object 

## Q3
locks are used to lock a full block of code, this prevents race conditions as a thread locks the section it's working on when it reaches it

therefore we should use locks when we wan't to prevent race conditions for a block of code  
and use atomic variables on more basic things
## Q4
this situation may occur because if there are too many threads trying to access a shared resource performance drops because workers sleep more than they do work

#### Usage of locks
locks make a block of code completely unaccessible for every thread except one
this would cause those other threads to sleep meaning even if you have 100 threads only one of them can work at a time.

#### Usage of atomic variables
when we use atomic variables and atomic operations, at a curtain point many threads my try to change one variable   
only one can successfully access the variable, meaning the rest would fail  
this failure is just waste of cpu power.

## Q5
majority of mainstream cpus has something around 8 to 16 cores right now  
this means one thing  
logically we can't run 100 threads at the same time in parallel  
this causes something known as context switching: cpu cores need to switch between threads 
and this continuous switching would make the program slow


cpu cores have their own distinct cache, when a core changes a synced variable all the cpu cores change their cache value of that variable
when we have too many threads working on a synced variable, cpu cores constantly follow the changes in order to keep their cache synced 
this would cause cpu cores to spend more time synchronizing the cache rather than processing the threads 
this situation is known cache coherence synchronization overhead.

## Q6
Deadlocks are rare because you can't determine threads execution order, OS determines this by itself
to increase likelihood of deadlocks to happen a developer can start too many threads and delay the threads