Develop #1

Open
farnam_jhn wants to merge 2 commits from develop into main
3 changed files with 136 additions and 5 deletions
+54
View File
@@ -0,0 +1,54 @@
# 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
+42
View File
@@ -0,0 +1,42 @@
package bonus;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicVsNormal {
private static int counter = 0;
private static AtomicInteger atomicCounter = new AtomicInteger(0);
public static void main(String[] args){
int incrementCount = 1000;
List<Thread> workers = new ArrayList<>();
for (int i = 0; i < 10; i++){
workers.add(new Thread(() ->{
for (int j = 0; j < incrementCount; j++) {
counter++;
atomicCounter.incrementAndGet();
}
}
));
}
for (Thread worker : workers){
worker.start();
}
for (Thread worker : workers){
try {
worker.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("Expected: 10000");
System.out.println("Normal increment: " + counter);
System.out.println("Atomic increment: " + atomicCounter.get());
}
}
@@ -1,10 +1,14 @@
package dev.banking.model;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
private final int accountId;
private long balance;
private final Lock lock = new ReentrantLock();
/*
* Students may introduce additional fields
* such as:
@@ -32,7 +36,13 @@ public class BankAccount {
* - Should not block unnecessarily if using read/write locks
*/
public long getBalance() {
throw new UnsupportedOperationException("TODO: implement thread-safe balance read");
lock.lock();
try {
return balance;
}
finally {
lock.unlock();
}
}
/*
@@ -43,7 +53,13 @@ public class BankAccount {
* - Must not lose updates under concurrency
*/
public void deposit(long amount) {
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
lock.lock();
try{
balance += amount;
}
finally {
lock.unlock();
}
}
/*
@@ -56,7 +72,13 @@ public class BankAccount {
* to extend the system (optional)
*/
public void withdraw(long amount) {
throw new UnsupportedOperationException("TODO: implement thread-safe withdraw");
lock.lock();
try {
balance -= amount;
}
finally {
lock.unlock();
}
}
/*
@@ -73,6 +95,19 @@ public class BankAccount {
* - Or tryLock with retry strategy
*/
public void transfer(BankAccount target, long amount) {
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer");
BankAccount first = this.getAccountId() > target.getAccountId() ? target : this;
BankAccount second = this.getAccountId() > target.getAccountId() ? this : target;
first.lock.lock();
second.lock.lock();
try {
this.balance -= amount;
target.balance += amount;
}
finally {
second.lock.unlock();
first.lock.unlock();
}
}
}