I was recently trying to wrap my head around some Java multi-threading concepts and was writing a small piece of code to help me understand memory visibility and getting synchronization as correct as possible. Based on what I've read, it seems that the smaller the amount of code we hold a lock around, the more efficient our program will be (in general). I've written a small class to help me understand some of the synchronization issues I might run into:
public class BankAccount {
private int balance_;
public BankAccount(int initialBalance) {
if (initialBalance < 300) {
throw new IllegalArgumentException("Balance needs to be at least 300");
}
balance_ = initialBalance;
}
public void deposit(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit has to be positive");
}
// should be atomic assignment
// copy should also be non-shared as it's on each thread's stack
int copy = balance_;
// do the work on the thread-local copy of the balance. This work should
// not be visible to other threads till below synchronization
copy += amount;
synchronized(this) {
balance_ = copy; // make the new balance visible to other threads
}
}
public void withdraw(int amount) {
// should be atomic assignment
// copy should also be non-shared as it's on each thread's stack
int copy = balance_;
if (amount > copy) {
throw new IllegalArgumentException("Withdrawal has to be <= current balance");
}
copy -= amount;
synchronized (this) {
balance_ = copy; // update the balance and make it visible to other threads.
}
}
public synchronized getBalance() {
return balance_;
}
}
Please ignore the fact that balance_ should be a double instead of an integer. I know that primitive type reads/assignments are atomic with the exception of doubles and longs so I opted for ints for simplicity
I've tried to write comments inside of the functions to describe my thinking. This class was written to get correct synchronization as well as minimize the amount of code that's under locking. Here's my questions:
Any code that is not inside the synchronized block can be concurrenlty executed by multiple threads, your solution is creating the new balance outside of a synchronized block so it won't work properly. Let's see an example:
int copy = balance_; // 1
copy += amount; //2
synchronized(this) {
balance_ = copy; // 3
}
At the end the BankAccount has 20 but it should be 35
This is the correct way to make it:
public class BankAccount {
private int balance_;
public BankAccount(int initialBalance) {
if (initialBalance < 300) {
throw new IllegalArgumentException("Balance needs to be at least 300");
}
balance_ = initialBalance;
}
public void deposit(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit has to be positive");
}
synchronized(this) {
balance_ += amount;
}
}
public void withdraw(int amount) {
synchronized (this) {
if (amount > balance_) {
throw new IllegalArgumentException("Withdrawal has to be <= current balance");
}
balance_ -= amount;
}
}
public synchronized int getBalance() {
return balance_;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With