Compare commits
3
Commits
fa95f2ebeb
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0128c5b759 | ||
|
|
3d4c6501ad | ||
|
|
bc89c45143 |
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="25" 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>
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
1 - What are atomic variables?
|
||||||
|
|
||||||
|
Atomic variables in Java are thread-safe variables that perform operations like read, write, and increment atomically. Their purpose is to safely share data between threads without locks for simple operations. Unlike ordinary variables, atomic variables prevent race conditions during simultaneous access.
|
||||||
|
|
||||||
|
2 - Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types.
|
||||||
|
|
||||||
|
AtomicInteger is commonly used as a thread-safe counter, for example to count how many requests or tasks have been processed by multiple threads.
|
||||||
|
AtomicLong
|
||||||
|
AtomicBoolean
|
||||||
|
AtomicReference
|
||||||
|
|
||||||
|
3 - Compare locks with atomic variables.
|
||||||
|
|
||||||
|
Atomic variables are best for simple, single-variable operations such as incrementing a counter or updating a flag. They are usually faster and lighter.
|
||||||
|
Locks are better when a program needs to protect multiple operations or multiple variables as one critical section. A lock ensures that only one thread can execute that block of code at a time, which is useful for more complex logic.
|
||||||
|
|
||||||
|
Use atomic variables for simple thread-safe operations on a single value.
|
||||||
|
Use locks when you need to protect more complex shared state or a larger block of code.
|
||||||
|
|
||||||
|
4 - A program is completely free of race conditions but still performs poorly under high contention.
|
||||||
|
|
||||||
|
Threads block waiting for locks, turning parallel execution into sequential processing. CPU time is wasted managing queues rather than computing.
|
||||||
|
Frequent blocking/unblocking forces the OS to save/restore thread states. High switch rates consume CPU cycles needed for actual work.
|
||||||
|
Unrelated variables in the same cache line cause unnecessary cache invalidations across cores, forcing slow main memory accesses despite logical independence.
|
||||||
|
|
||||||
|
5 - Many concurrent systems experience performance degradation as the number of threads increases.
|
||||||
|
|
||||||
|
Adding more threads does not always improve performance because after a certain point, the extra threads create more overhead.
|
||||||
|
Context switching: if there are more runnable threads than CPU cores, the OS must frequently pause one thread and run another. This switching costs time and can reduce overall throughput.
|
||||||
|
Contention: many threads may compete for the same shared resources, such as locks, atomic variables, memory, or I/O. As contention increases, threads spend more time waiting than working.
|
||||||
|
Cache coherence: when multiple cores repeatedly read and write shared data, the CPU must keep their caches consistent. This creates extra communication between cores and slows memory access.
|
||||||
|
Synchronization overhead: locks, mutexes, barriers, and atomic operations all add coordination costs. Even when they guarantee correctness, they can reduce scalability under heavy load.
|
||||||
|
|
||||||
|
6 - Deadlocks often only appear in production, not during testing.
|
||||||
|
|
||||||
|
Deadlocks may appear in production but not during testing because thread scheduling is non-deterministic. A deadlock happens only when threads acquire locks in a particular timing and order. During testing, that exact interleaving may never occur, but in production different timing, higher load, more threads, and different hardware can make it happen.
|
||||||
|
Two ways to make deadlocks more likely during testing are:
|
||||||
|
Increase concurrency and repetition
|
||||||
|
Run the program many times with more threads, heavier load, and stress tests. More executions create more possible thread interleavings, which increases the chance of reaching the deadlock situation.
|
||||||
|
Add artificial delays around lock operations
|
||||||
@@ -1,78 +1,58 @@
|
|||||||
package dev.banking.model;
|
package dev.banking.model;
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
public class BankAccount {
|
public class BankAccount {
|
||||||
|
|
||||||
private final int accountId;
|
private final int accountId;
|
||||||
private long balance;
|
private long balance;
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
/*
|
|
||||||
* Students may introduce additional fields
|
|
||||||
* such as:
|
|
||||||
* - Lock / ReentrantLock
|
|
||||||
* - ReadWriteLock
|
|
||||||
* - Object monitor
|
|
||||||
* - etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public BankAccount(int accountId, long initialBalance) {
|
public BankAccount(int accountId, long initialBalance) {
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
this.balance = initialBalance;
|
this.balance = initialBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAccountId() {
|
public int getAccountId() {
|
||||||
return accountId;
|
return accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO:
|
|
||||||
* Return the current balance in a thread-safe way.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - Must be safe under concurrent reads/writes
|
|
||||||
* - 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO:
|
|
||||||
* Increase balance atomically.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - Must not lose updates under concurrency
|
|
||||||
*/
|
|
||||||
public void deposit(long amount) {
|
public void deposit(long amount) {
|
||||||
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
|
lock.lock();
|
||||||
|
try {
|
||||||
|
balance += amount;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO:
|
|
||||||
* Decrease balance atomically.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - Must not cause race conditions
|
|
||||||
* - Negative balance handling is NOT required unless you decide
|
|
||||||
* to extend the system (optional)
|
|
||||||
*/
|
|
||||||
public void withdraw(long amount) {
|
public void withdraw(long amount) {
|
||||||
throw new UnsupportedOperationException("TODO: implement thread-safe withdraw");
|
lock.lock();
|
||||||
|
try {
|
||||||
|
balance -= amount;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO:
|
|
||||||
* Transfer money between two accounts atomically.
|
|
||||||
*
|
|
||||||
* IMPORTANT REQUIREMENTS:
|
|
||||||
* - Must be atomic (no partial transfer)
|
|
||||||
* - Must be deadlock-free
|
|
||||||
* - Must protect both source and target accounts
|
|
||||||
*
|
|
||||||
* HINT:
|
|
||||||
* - Consider global lock ordering using accountId
|
|
||||||
* - 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user