Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 38 additions & 10 deletions src/main/java/Banking/BankAccount.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,57 @@ public BankAccount(int id, int initialBalance) {
this.balance = initialBalance;
}

public int getId(){
return id;
public int getId() {
return id;
}

public int getBalance() {
// TODO: Consider locking (if needed)
return balance;
lock.lock();
try {
return balance;
} finally {
lock.unlock();
}
}

public Lock getLock() {
return lock;
}

public void deposit(int amount) {
// TODO: Safely add to balance.
lock.lock();
try {
balance += amount;
} finally {
lock.unlock();
}
}

public void withdraw(int amount) {
// TODO: Safely withdraw from balance.
lock.lock();
try {
balance -= amount;
} finally {
lock.unlock();
}
}

public void transfer(BankAccount target, int amount) {
// TODO: Safely make the changes
// HINT: Both accounts need to be locked, while the changes are being made
// HINT: Be cautious of potential deadlocks.
// Acquire locks in consistent order to prevent deadlock
BankAccount first = this.id < target.id ? this : target;
BankAccount second = this.id < target.id ? target : this;

first.lock.lock();
try {
second.lock.lock();
try {
this.balance -= amount;
target.balance += amount;
} finally {
second.lock.unlock();
}
} finally {
first.lock.unlock();
}
}
}
}
101 changes: 81 additions & 20 deletions src/main/java/Banking/BankingMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,99 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class BankingMain {
private static final int INITIAL_BALANCE = 20000;
private static final int NUM_ACCOUNTS = 4;
private static final long TIMEOUT_MINUTES = 2;

public List<BankAccount> calculate() throws InterruptedException {
System.out.println("Initializing banking system with " + NUM_ACCOUNTS + " accounts...");

List<BankAccount> accounts = initializeAccounts();
Thread[] threads = createAndStartThreads(accounts);

waitForThreadsCompletion(threads);

System.out.println("\nAll transactions processed successfully!");
System.out.println("Total transactions processed: " + TransactionProcessor.getTransactionCount());

return accounts;
}

private List<BankAccount> initializeAccounts() {
List<BankAccount> accounts = new ArrayList<>();
accounts.add(new BankAccount(1,20000));
accounts.add(new BankAccount(2,20000));
accounts.add(new BankAccount(3,20000));
accounts.add(new BankAccount(4,20000));

Thread[] threads = new Thread[4];
for(int i = 1; i <= 4; i++){
String fileName = i + ".txt";
threads[i - 1] = new Thread(new TransactionProcessor(accounts.get(i - 1), fileName, accounts));
for (int i = 1; i <= NUM_ACCOUNTS; i++) {
accounts.add(new BankAccount(i, INITIAL_BALANCE));
System.out.printf("Account %d initialized with balance: %,d%n", i, INITIAL_BALANCE);
}
return accounts;
}

for(Thread thread : threads){
thread.start();
private Thread[] createAndStartThreads(List<BankAccount> accounts) {
Thread[] threads = new Thread[NUM_ACCOUNTS];
for (int i = 0; i < NUM_ACCOUNTS; i++) {
String fileName = (i + 1) + ".txt";
threads[i] = new Thread(new TransactionProcessor(accounts.get(i), fileName, accounts));
threads[i].setName("TransactionProcessor-" + (i + 1));
threads[i].start();
System.out.println("Started thread for " + threads[i].getName());
}
return threads;
}

private void waitForThreadsCompletion(Thread[] threads) throws InterruptedException {
long startTime = System.currentTimeMillis();

for(Thread thread : threads){
thread.join();
for (Thread thread : threads) {
thread.join(TimeUnit.MINUTES.toMillis(TIMEOUT_MINUTES));

if (thread.isAlive()) {
System.err.println("Warning: Thread " + thread.getName() + " did not complete within timeout!");
}
}

long duration = System.currentTimeMillis() - startTime;
System.out.printf("\nProcessing completed in %.2f seconds%n", duration / 1000.0);
}

return accounts;
public static void main(String[] args) {
try {
BankingMain main = new BankingMain();
System.out.println("\nStarting banking simulation...\n");

List<BankAccount> accounts = main.calculate();

System.out.println("\nFinal Account Balances:");
for (BankAccount account : accounts) {
System.out.printf("Account %d: %,d%n",
account.getId(), account.getBalance());
}

verifyTotalBalance(accounts, NUM_ACCOUNTS * INITIAL_BALANCE);

} catch (InterruptedException e) {
System.err.println("Banking simulation was interrupted: " + e.getMessage());
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println("Error in banking simulation: " + e.getMessage());
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
BankingMain main = new BankingMain();
List<BankAccount> accounts = main.calculate();
for(BankAccount account : accounts){
System.out.println("Final balance of Account Number " + account.getId() + " : " + account.getBalance());

private static void verifyTotalBalance(List<BankAccount> accounts, int expectedTotal) {
int actualTotal = accounts.stream()
.mapToInt(BankAccount::getBalance)
.sum();

System.out.printf("\nBalance Verification:%nExpected Total: %,d%nActual Total: %,d%n",
expectedTotal, actualTotal);

if (actualTotal != expectedTotal) {
System.err.println("WARNING: Balance discrepancy detected!");
} else {
System.out.println("Balance verification successful!");
}
}
}
}
121 changes: 96 additions & 25 deletions src/main/java/Banking/TransactionProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
import java.io.InputStreamReader;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TransactionProcessor implements Runnable {
private final BankAccount account;
private final String fileName;
private final List<BankAccount> allAccounts;
private final Lock logLock = new ReentrantLock();
private static int transactionCounter = 0;
private static final Lock counterLock = new ReentrantLock();

public TransactionProcessor(BankAccount account, String fileName, List<BankAccount> allAccounts) {
this.account = account;
Expand All @@ -18,37 +23,103 @@ public TransactionProcessor(BankAccount account, String fileName, List<BankAccou

@Override
public void run() {
InputStreamReader is = new InputStreamReader(
logWithLock("Starting processing for account " + account.getId());

try (InputStreamReader is = new InputStreamReader(
Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(fileName)));
try (BufferedReader reader = new BufferedReader(is)) {
BufferedReader reader = new BufferedReader(is)) {

String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.trim().split("\\s+");
if (parts.length == 0) continue;

switch (parts[0]) {
case "Deposit":
int depositAmount = Integer.parseInt(parts[1]);
account.deposit(depositAmount);
break;

case "Withdraw":
int withdrawAmount = Integer.parseInt(parts[1]);
account.withdraw(withdrawAmount);
break;

case "Transfer":
int targetIndex = Integer.parseInt(parts[1])-1;
int transferAmount = Integer.parseInt(parts[2]);

BankAccount target = allAccounts.get(targetIndex);
account.transfer(target, transferAmount);
break;
}
processTransactionLine(line.trim());
}

} catch (Exception e) {
logWithLock("Error processing file " + fileName + ": " + e.getMessage());
e.printStackTrace();
} finally {
logWithLock("Completed processing for account " + account.getId());
}
}
}

private void processTransactionLine(String line) {
if (line.isEmpty()) return;

String[] parts = line.split("\\s+");
String transactionType = parts[0];

try {
switch (transactionType) {
case "Deposit":
int depositAmount = Integer.parseInt(parts[1]);
account.deposit(depositAmount);
logTransaction(transactionType, depositAmount);
break;

case "Withdraw":
int withdrawAmount = Integer.parseInt(parts[1]);
account.withdraw(withdrawAmount);
logTransaction(transactionType, withdrawAmount);
break;

case "Transfer":
int targetIndex = Integer.parseInt(parts[1]) - 1;
int transferAmount = Integer.parseInt(parts[2]);
BankAccount target = allAccounts.get(targetIndex);
account.transfer(target, transferAmount);
logTransfer(transferAmount, target);
break;

default:
logWithLock("Unknown transaction type: " + transactionType);
}

incrementTransactionCounter();

} catch (NumberFormatException e) {
logWithLock("Invalid number format in transaction: " + line);
} catch (IndexOutOfBoundsException e) {
logWithLock("Invalid target account in transfer: " + line);
} catch (Exception e) {
logWithLock("Error processing transaction: " + line + " - " + e.getMessage());
}
}

private void logTransaction(String type, int amount) {
logWithLock(String.format("Processed %s of %d for account %d. New balance: %d",
type, amount, account.getId(), account.getBalance()));
}

private void logTransfer(int amount, BankAccount target) {
logWithLock(String.format("Transfer %d from account %d to %d. Balances: %d -> %d",
amount, account.getId(), target.getId(),
account.getBalance(), target.getBalance()));
}

private void logWithLock(String message) {
logLock.lock();
try {
System.out.println("[Thread-" + Thread.currentThread().getId() + "] " + message);
} finally {
logLock.unlock();
}
}

private void incrementTransactionCounter() {
counterLock.lock();
try {
transactionCounter++;
} finally {
counterLock.unlock();
}
}

public static int getTransactionCount() {
counterLock.lock();
try {
return transactionCounter;
} finally {
counterLock.unlock();
}
}
}
Loading