This commit is contained in:
Arefe Talebi
2026-06-29 17:02:08 +03:30
parent fa95f2ebeb
commit 4b1f872781
8 changed files with 203 additions and 4 deletions
+10
View File
@@ -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/
+13
View File
@@ -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>
+7
View File
@@ -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>
+20
View File
@@ -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>
+12
View File
@@ -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
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+93
View File
@@ -0,0 +1,93 @@
HW09-Theoretical Questions
1. What are atomic variables?
Atomic variables are variables that can be safely used in multithreaded programs without using locks or synchronized blocks. Their operations are performed atomically, which means that a thread can complete an operation without interference from other threads.
The main purpose of atomic variables is to prevent race conditions. Unlike normal variables, atomic variables guarantee that updates are done safely when several threads access the same variable at the same time.
2. Name at least four classes from the "java.util.concurrent.atomic" package.
Examples of atomic classes are:
"AtomicInteger"
"AtomicLong"
"AtomicBoolean"
"AtomicReference"
A common use of "AtomicInteger" is implementing a shared counter. Multiple threads can increment the counter safely without using locks.
3. Compare locks with atomic variables.
Atomic variables are usually faster because they do not block threads. They are suitable for simple operations on a single variable, such as increasing a counter.
Locks are more suitable when several operations or multiple shared variables must be protected together. Although locks have more overhead, they provide greater flexibility.
In short, atomic variables are better for simple tasks, while locks are preferred for more complex critical sections.
4. A program is completely free of race conditions but still performs poorly under high contention. Explain.
A program may be correct but still have poor performance when many threads are running simultaneously.
Some factors that can reduce performance are:
1. Lock contention: many threads may wait for the same lock.
2. Synchronization overhead: locking and unlocking repeatedly adds extra cost.
3. Context switching: the operating system spends time switching between threads instead of executing them.
Therefore, correctness does not always mean good scalability.
5. Why does adding more threads not always improve performance?
Adding more threads is not always beneficial because threads also create overhead.
- Context switching: frequent switching between threads consumes CPU time.
- Contention: threads may compete for shared resources.
- Cache coherence: changes made by one core may force other cores to update their caches.
- Synchronization overhead: synchronization mechanisms themselves require additional processing.
As a result, after a certain point, increasing the number of threads may even decrease performance.
6. Why do deadlocks often appear only in production?
Deadlocks depend on thread scheduling, which is unpredictable. During testing, the program may run without problems because threads happen to execute in a safe order. In production, different workloads and execution timings can expose deadlocks.
Two ways to increase the chance of finding deadlocks during testing are:
1. Running stress tests with many threads and many operations.
2. Running tests repeatedly and adding random delays to create different execution orders.
Bonus Task
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo {
static int normalCounter = 0;
static AtomicInteger atomicCounter = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
normalCounter++;
atomicCounter.incrementAndGet();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Normal Counter: " + normalCounter);
System.out.println("Atomic Counter: " + atomicCounter.get());
}
}
@@ -1,4 +1,5 @@
package dev.banking.model;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
@@ -13,6 +14,7 @@ public class BankAccount {
* - Object monitor
* - etc.
*/
private final ReentrantLock lock = new ReentrantLock();
public BankAccount(int accountId, long initialBalance) {
this.accountId = accountId;
@@ -32,7 +34,12 @@ 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 +50,12 @@ 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 +68,12 @@ 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 +90,27 @@ 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 firstLock;
BankAccount secondLock;
if (this.accountId < target.accountId) {
firstLock = this;
secondLock = target;
} else {
firstLock = target;
secondLock = this;
}
firstLock.lock.lock();
secondLock.lock.lock();
try {
this.balance -= amount;
target.balance += amount;
} finally {
secondLock.lock.unlock();
firstLock.lock.unlock();
}
}
}