HW-09 — Advanced Multithreading
📋 Introduction
This assignment is divided into two main sections:
Theoretical Questions:
- You are asked to answer questions about multithreading concepts.
Practical Project:
- You are asked to implement a banking system that performs concurrent operations using multithreading. The main goal of the exercise is handling deadlocks and race conditions so that the program performs correctly.
🧠 Theoretical Questions
Note:
Write your answers in a Markdown file (e.g. Answers.md) and place it in the root directory of your forked repository.
⚛️ Atomic Variables & Synchronization
1 - What are atomic variables?
Explain their purpose and how they differ from ordinary (non-atomic) variables.
2 - Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types.
For one of them, briefly describe a typical use case.
3 - Compare locks with atomic variables.
In which scenarios is using a lock a better choice than an atomic variable, and vice versa?
🎯Bonus Task:
Write a small Java program that concurrently increments a normal int variable and an AtomicInteger variable from multiple threads. Print the final values of both variables to demonstrate that the atomic variable produces the correct result, while the normal variable may show an inconsistent value due to a race condition.
🔒 Locks & Concurrent Design
4 - A program is completely free of race conditions but still performs poorly under high contention.
Explain how this situation can occur.
Discuss at least three concurrency-related factors that may limit scalability even when correctness is guaranteed.
5 - Many concurrent systems experience performance degradation as the number of threads increases.
Explain why adding more threads does not always improve performance.
Your answer should discuss concepts such as:
Context switching Contention Cache coherence Synchronization overhead
⚠️ Deadlocks
6 - Deadlocks often only appear in production, not during testing.
Explain why this might happen from a thread-scheduling perspective.
Describe two strategies a developer can use to increase the likelihood of exposing deadlocks during testing.
💻 Practical Project
🏦 Advanced Banking & Concurrent Transaction System
🧭 Overview
In modern banking systems, thousands of transactions are processed concurrently by multiple worker threads. These threads may access shared bank accounts simultaneously, leading to potential race conditions and consistency issues.
In this assignment, you will implement the core concurrency control layer of a banking simulation system.
Your goal is to ensure:
- Correctness under concurrent execution
- consistent account state under concurrent execution
- Deadlock-free transfer operations
- Reasonable concurrency and scalability
Students are expected to design their own synchronization strategy from scratch.
📦 Provided System (DO NOT MODIFY)
The following components are fully implemented and must not be changed:
✅ Infrastructure Layer
- Transaction generation and models
- TransactionProcessor (dispatch layer)
- BankingSystem (task submission layer)
- ExecutorService configuration
- Worker thread execution model
- DemoApplication (system runner)
- Live monitoring (debug tool)
- JUnit test suite
❗ Your Responsibility (IMPORTANT)
You are ONLY responsible for implementing thread-safe logic inside:
📄 BankAccount.java
You must implement:
deposit(long amount);
withdraw(long amount);
transfer(BankAccount target, long amount);
getBalance();
Additionally, you may introduce internal synchronization design choices such as:
- Locks
- ReadWriteLock
- Atomic variables
- Custom ordering strategies
🏗 System Architecture
Transaction Stream
│
▼
TransactionProcessor
│
▼
BankingSystem (task submission via ExecutorService)
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Worker A Worker B Worker C
│ │ │
└──────────────┼──────────────┘
▼
Shared Bank Accounts
Multiple worker threads may operate on the same account simultaneously.
Execution order is non-deterministic, and correctness must hold for all possible schedules.
🧠 Core Design Requirement
Your solution must guarantee:
- No race conditions
- No lost updates
- Atomic multi-step operations
- Deadlock-free execution under all conditions
🛠 Implementation Phases
🟢 Phase 1 — Thread-Safe Account State
Implement safe access and mutation for account balance:
Methods:
getBalance()deposit()withdraw()
Requirements:
- No race conditions
- Reads must never observe invalid intermediate state
- Multiple accounts must remain independently concurrent
- Negative balances are allowed unless explicitly handled by your implementation.
🔵 Phase 2 — Atomic Transfers
Implement:
transfer(BankAccount target, long amount);
Requirements:
- Transfer must be fully atomic
- No partial updates allowed
- Money must never be lost or created
- Consistency must hold under concurrent transfers
Important:
A transfer involves two shared resources (accounts), which introduces synchronization complexity beyond simple locking.
🔴 Phase 3 — Deadlock-Free Design
Concurrent transfers may create cyclic locking scenarios:
Thread 1: A → B
Thread 2: B → A
Requirements:
- System must be completely deadlock-free
- Must pass high-concurrency stress tests
- Must remain correct under arbitrary execution ordering
⚙ Allowed Concurrency Tools
You may use:
synchronizedReentrantLockReentrantReadWriteLockConditionAtomic classesjava.util.concurrentutilities
❌ Restrictions
You are NOT allowed to:
- Use busy waiting (e.g.,
while(true)) - Modify test files
- Modify method signatures
- Change system architecture outside
BankAccount - Create additional worker threads
- Modify transaction processing pipeline
📊 Live Monitoring (Debug Support)
A monitoring system is included to display real-time account balances.
It helps you:
- Observe race conditions
- Debug concurrency issues
- Validate correctness under load
⚠ This component is NOT part of grading and may produce non-deterministic output.
🧪 Testing
A full JUnit test suite validates your solution.
Your implementation will be evaluated on:
✅ Correctness
- No race conditions
- No lost updates
- Correct final balances
🧨 Robustness
- No deadlocks under stress tests
- Stable execution under high concurrency
- Correct behavior under unpredictable scheduling
⚡ Performance
- Independent accounts should not block each other
- Avoid unnecessary global locking
- Maintain scalable concurrency
🌟 Bonus Challenge (Optional)
Implement conditional waiting for insufficient funds:
withdraw()waits until balance is sufficienttransfer()waits until source has enough funds
Requirements:
- No busy waiting
- No CPU spinning
- No starvation
- Must remain thread-safe
⚠ Note on Bonus: The provided JUnit test suite assumes the base behavior (allowing balances to proceed without waiting). Implementing the bonus challenge might cause some base stress tests to time out due to threads waiting indefinitely for funds. It is recommended to verify the bonus logic using your own custom test scenarios.
💡 Final Hint
A correct solution is always more valuable than an optimized incorrect one.
Start simple, ensure correctness, then improve concurrency and performance.