Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7db85c89d |
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
Generated
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="HW-09-Advanced-Multithreading" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+20
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://mirror-maven.runflare.com/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="21" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Answers of questions
|
||||||
|
|
||||||
|
## ⚛️ Atomic Variables & Synchronization:
|
||||||
|
|
||||||
|
### 1:
|
||||||
|
**Atomic variables** are special variables which
|
||||||
|
enable thread-safe updates to single variables without the high overhead of explicit locking mechanisms (like **synchronized blocks** or **ReentrantLock**).
|
||||||
|
the entire operation happens as a single, indivisible unit of work without interruption from other threads.
|
||||||
|
unlike normal variables they prevent **race condition** and ensuring that changes made by one thread are immediately visible to all other threads.
|
||||||
|
Ordinary variables require explicit locks to be thread-safe. Atomic variables rely on hardware-level Compare-And-Swap (CAS) CPU instructions, which are low-level, non-blocking operations.
|
||||||
|
|
||||||
|
|
||||||
|
### 2:
|
||||||
|
|
||||||
|
- AtomicInteger
|
||||||
|
- AtomicLong
|
||||||
|
- AtomicBoolean
|
||||||
|
- AtomicReference
|
||||||
|
|
||||||
|
##### Typical Use Case for AtomicLong:
|
||||||
|
AtomicLong is frequently used to implement a global transaction ID generator or a high-throughput request counter in web servers.
|
||||||
|
|
||||||
|
>Example: Every time a new user request or bank transaction enters the system, the server calls atomicLong.incrementAndGet(). Because this operation is thread-safe and lock-free, thousands of concurrent requests can safely receive a unique, sequential ID without blocking each other or causing performance bottlenecks.
|
||||||
|
|
||||||
|
### 3:
|
||||||
|
|
||||||
|
#### When a Lock is a better choice:
|
||||||
|
- State Interdependency: When you need to update multiple variables together as a single atomic unit (e.g., updating both a sourceAccount and a targetAccount balance during a transfer).
|
||||||
|
|
||||||
|
|
||||||
|
- Complex Business Logic: When the state change involves conditions or complex calculations that cannot be expressed as a simple single-value swap.
|
||||||
|
|
||||||
|
|
||||||
|
- High Contention over Long Operations: If threads need to hold onto a resource for a relatively long duration, blocking other threads is cleaner than letting them waste CPU cycles retrying.
|
||||||
|
|
||||||
|
#### When an Atomic Variable is a better choice:
|
||||||
|
|
||||||
|
- Single-Variable Mutations: Counters, sequence generators, state flags, or simple status trackers.
|
||||||
|
|
||||||
|
|
||||||
|
- Low-to-Moderate Contention: When collisions between threads are infrequent, atomic variables are significantly faster because they avoid the overhead of putting threads to sleep and waking them back up.
|
||||||
|
|
||||||
|
## 🔒 Locks & Concurrent Design
|
||||||
|
|
||||||
|
### 4:
|
||||||
|
|
||||||
|
This situation occurs because **correctness does not guarantee efficiency**. A program can be 100% correct by over-synchronizing (e.g., using a single global lock for the entire application). While this eliminates race conditions by forcing serial execution, it introduces severe performance bottlenecks.
|
||||||
|
|
||||||
|
Three concurrency-related factors that limit scalability are:
|
||||||
|
|
||||||
|
- **Lock Contention:** When multiple threads frequently attempt to acquire the same lock simultaneously, most of them are blocked, forced into a waiting state. This serializes execution, stripping away the benefits of a multi-core processor.
|
||||||
|
|
||||||
|
|
||||||
|
- **Thread Amdahl's Law (Serial Fraction):** Scalability is bounded by the sequential portion of the program. If critical sections (locked code) take up a substantial percentage of runtime, adding more processors yields diminishing returns.
|
||||||
|
|
||||||
|
|
||||||
|
- **Context Switching Overhead:** When threads compete for a pessimistic lock, the operating system must repeatedly pause thread execution (saving CPU registers) and resume other threads. This constant switching consumes massive CPU cycles without doing actual application work.
|
||||||
|
|
||||||
|
### 5:
|
||||||
|
|
||||||
|
Adding more threads past the optimal threshold (usually proportional to the number of physical CPU cores) harms performance due to diminishing physical hardware resources and architectural bottlenecks:
|
||||||
|
|
||||||
|
|
||||||
|
- **Context Switching:** When the number of active threads exceeds the number of physical CPU cores, the OS must schedule threads using time-slicing. The overhead of constantly saving and loading thread states begins to eclipse actual execution time.
|
||||||
|
|
||||||
|
|
||||||
|
- **Contention:** More threads mean more active competitors for shared memory resources, queues, and locks. Threads spend more time waiting in queues than doing productive work.
|
||||||
|
|
||||||
|
|
||||||
|
- **Cache Coherence (Cache Thrashing):** Modern CPUs use L1/L2/L3 caches. When multiple threads across different cores modify the same shared variables, the cores must continuously invalidate each other's caches to keep memory uniform. This results in heavy memory-bus traffic and slow reads directly from the main RAM.
|
||||||
|
|
||||||
|
|
||||||
|
- **Synchronization Overhead:** Managing internal thread structures, lock acquisition queues, and tracking atomic states introduces computational overhead that expands non-linearly with thread count.
|
||||||
|
|
||||||
|
## ⚠️ Deadlocks
|
||||||
|
|
||||||
|
### 6:
|
||||||
|
|
||||||
|
In testing environments, the workload is often lower, the timing uniform, and the execution faster. The OS thread scheduler non-deterministically chooses which thread runs based on thread priorities and CPU availability. In simple test environments, threads often finish their tasks completely before the next thread even reaches the critical section, masking potential interleaving defects. Production introduces unpredictable delays (network latency, disk I/O, heavy load), which change thread interleaving patterns and trigger the cyclic dependencies.
|
||||||
|
|
||||||
|
#### Strategies to expose deadlocks during testing:
|
||||||
|
|
||||||
|
- Introduce Artificial Delays (Thread.sleep() or Thread.yield()): Injecting randomized short sleeps or yields inside critical sections—right after acquiring the first lock but before acquiring the second—forces alternative thread interleaving schedules and exposes timing vulnerabilities.
|
||||||
|
|
||||||
|
|
||||||
|
- High-Concurrency Stress Testing with Shuffled Inputs: Write automated test suites that spawn hundreds of threads performing inverse operations simultaneously (e.g., half the threads transfer A -> B and the other half transfer B -> A). Randomizing the transaction pairings under massive parallel load drastically increases the statistical probability of a cyclic lock conflict occurring.
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class AtomicVariables {
|
||||||
|
public static int normalInt = 0;
|
||||||
|
public static AtomicInteger atomicInt = new AtomicInteger(0);
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
Thread[] threads = new Thread[10];
|
||||||
|
|
||||||
|
// Create and start 10 threads
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
threads[i] = new Thread(() -> {
|
||||||
|
for (int j = 0; j < 10000; j++) {
|
||||||
|
normalInt++;
|
||||||
|
atomicInt.incrementAndGet();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all threads to complete
|
||||||
|
for (Thread t : threads) {
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Expected value: 100000");
|
||||||
|
System.out.println("Normal integer: " + normalInt);
|
||||||
|
System.out.println("Atomic integer: " + atomicInt.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package dev.banking.model;
|
package dev.banking.model;
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.*;
|
||||||
|
|
||||||
public class BankAccount {
|
public class BankAccount {
|
||||||
|
|
||||||
private final int accountId;
|
private final int accountId;
|
||||||
@@ -14,6 +16,9 @@ public class BankAccount {
|
|||||||
* - etc.
|
* - etc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
|
private final Condition sufficientFunds = lock.newCondition();
|
||||||
|
|
||||||
public BankAccount(int accountId, long initialBalance) {
|
public BankAccount(int accountId, long initialBalance) {
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
this.balance = initialBalance;
|
this.balance = initialBalance;
|
||||||
@@ -32,7 +37,13 @@ public class BankAccount {
|
|||||||
* - Should not block unnecessarily if using read/write locks
|
* - Should not block unnecessarily if using read/write locks
|
||||||
*/
|
*/
|
||||||
public long getBalance() {
|
public long getBalance() {
|
||||||
throw new UnsupportedOperationException("TODO: implement thread-safe balance read");
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -43,7 +54,17 @@ public class BankAccount {
|
|||||||
* - Must not lose updates under concurrency
|
* - Must not lose updates under concurrency
|
||||||
*/
|
*/
|
||||||
public void deposit(long amount) {
|
public void deposit(long amount) {
|
||||||
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
|
if(amount <= 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
this.balance += amount;
|
||||||
|
sufficientFunds.signalAll();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -56,7 +77,21 @@ public class BankAccount {
|
|||||||
* to extend the system (optional)
|
* to extend the system (optional)
|
||||||
*/
|
*/
|
||||||
public void withdraw(long amount) {
|
public void withdraw(long amount) {
|
||||||
throw new UnsupportedOperationException("TODO: implement thread-safe withdraw");
|
if(amount <= 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lock.lock();
|
||||||
|
try{
|
||||||
|
while (this.balance < amount)
|
||||||
|
{
|
||||||
|
sufficientFunds.await();
|
||||||
|
}
|
||||||
|
this.balance -= amount;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -73,6 +108,26 @@ public class BankAccount {
|
|||||||
* - Or tryLock with retry strategy
|
* - Or tryLock with retry strategy
|
||||||
*/
|
*/
|
||||||
public void transfer(BankAccount target, long amount) {
|
public void transfer(BankAccount target, long amount) {
|
||||||
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer");
|
// BankAccount first = this.accountId < target.accountId ? this : target;
|
||||||
|
// BankAccount second = this.accountId < target.accountId ? target : this;
|
||||||
|
//
|
||||||
|
// first.lock.lock();
|
||||||
|
// try{
|
||||||
|
// second.lock.lock();
|
||||||
|
// try{
|
||||||
|
// this.withdraw(amount);
|
||||||
|
// target.deposit(amount);
|
||||||
|
// }
|
||||||
|
// finally {
|
||||||
|
// second.lock.unlock();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// finally {
|
||||||
|
// first.lock.unlock();
|
||||||
|
// }
|
||||||
|
|
||||||
|
//if we implement conditional waiting
|
||||||
|
this.withdraw(amount);
|
||||||
|
target.deposit(amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user