Immutability and Thread Safety in Java Part-2  

by ne on 2021-09-29 under Java

There are 4 sections of this Article:
Immutability and Thread Safety Part1- Covers Immutability
[This one] Immutability and Thread Safety Part2- Covers Thread Safety
Immutability and Thread Safety Part3- More on Thread Safety and Concurrency
Immutability and Thread Safety Part4 - Covers How the above 2 are related, and some further concepts

 

 

If you want to know the basics of a Thread , refer : Basics of Threads in Java

Think of a bank use case for instance, where a Father and Son has a joint account, so they can use the same account for their deposits and withdrawals.

Now, here, on a very basic level, the objects would be :

Father  ( of Customer class)

Son (of Customer class)

XYZ Bank  (of Bank class)

Acc-101  (of Account class)

 

Let's say the Person class is a simple class with name, account. And Bank class a basic class with Bank detail fields.

The Account class contains accountNumber and balance, and is supporting only two operations - withdraw, deposit.

And ideally, nobody should be able to withdraw money if the withdrawal amount is less than the account balance.

So, a typical Account class would be :


public class Account {
    int accountNumber;
    int balance;

    public int loadBalanceFromDatabase(){
        return Database.getBalance(accountNumber);
    }
    private void updateBalanceInDatabase(int balance){
        Database.updateBalance(accountNumber,balance);
    }
    private void provideTheAmountToCustomer(int amount){
        // do something
    }
    /**
     * Verifies if the account has enough credit, just passes the transaction
     * And decrements the balance with withdrawalAmount.
     */
    public boolean withdrawMoney(int withdrawalAmount){
        balance=loadBalanceFromDatabase();
        if(balance>=withdrawalAmount){
            provideTheAmountToCustomer(withdrawalAmount);  // allow the transaction
            balance=-withdrawalAmount;
            updateBalanceInDatabase(balance);
            
        }else{
            // Not enough credit
            return false;
        }
        return true;
    }

    /**
     * Credits the depositAmount to the account
     */
    public void depositMoney(int depositMoney){
        balance+=depositMoney;
    }
}

Now, let's think of a scenario here. Son goes to the bank, and withdraws money, and at the same time Father goes to another bank and tries to withdraw the money.

Since, two different processes started the transactions, We will have two different threads hitting our Bank server at the same time.

And, since we don't have any thread safety in place, there is a chance that we end up causing a lose to the bank.

Let's say, there is just Rs.100 in the balance of Acc-101.

Father and son both start the withdrawal operation at the same time, for Rs. 100.

And there is a chance of bank leading both of them to a successful transaction, and causing a loss at its end.

So, here we can see the need of Thread-Safety.

Thread Safety, means allowing Multithreaded applications to perform safely without leading to an undesired state. If multiple threads are reading/writing shared resources - it leads to undesired behavior.


balance=loadBalanceFromDatabase();
// Father thread, and Son thread, both come till here
// both have the balance value as : Rs. 100

if(balance>=withdrawalAmount){
// And Both threads, pass this check also

    provideTheAmountToCustomer(withdrawalAmount);
    // And Both are provided with the cash

    //...
    balance=-withdrawalAmount;
    // ...

Now, before proceeding to thread-safety direclty, let's talk about What exactly needs to be guarded, or which are the resources that we want to protect ?

We can have the following type of variables in java

  1. class variables with any of these access modifiers - private, public or protected
  2. class variables as final (with any of the access modifiers)
  3. class variables as static (with any of the access modifiers)
  4. Method variables (such as arguments, and variables declared inside a method)

Out of which, We can simply rule out Number :4  (Method variables), from our check list of safely handling variables for thread safety - a relief.

That is because, in Java, Local Variables (Method variables) are stored in thread-stack only (which is personal for each thread), and these are not shared across threads. So if it is not shared across threads, it won't cause any troubles.

Now moving on to Final class variables. As we discussed in previous part of this article that, final fields once assigned, can't be changed.

So, if final variables are assigned at the contruction time of an object only (in a constructor), then we can be sure afterwards, that multiple threads accessing these variables will not cause any issues, since it is a readonly field now.

So, we have 2 less things now to worry about.

Now, any class variable (which is not final) - needs to be accessed in a proper way - parallel read and writes should be made thread safe.

How can we do that ?

We can see that, if only one thread is allowed to enter a critical portion of code, then we won't have any issues.

For eg, if Father-thread and Son-thread were only allowed to call this withdrawalOperation in a serial fashion (first come first serve) basis, then we won't be facing any issues. We can achieve this by using synchronized keyword at method level.


public synchronized boolean withdrawMoney(int withdrawalAmount){

    balance=loadBalanceFromDatabase();
    if(balance>=withdrawalAmount){
        provideTheAmountToCustomer(withdrawalAmount);
        balance=-withdrawalAmount;
        updateBalanceInDatabase(balance);
    }else{
        // Not enough credit
        return false;
    }
    return true;
}

Now, no 2 threads can execute this method in parallel. So if Father-thread is withdrawing money, Son can only withdraw, if Father has completely finished the transaction.

This is known as making the entire critical section Atomic (for a thread).

But synchronizing the complete method, is not a good practice. There can be other non-critical heavy operations which can be a part of your method, and you may not have any issues executing them in parallel, why block them all.

Java provides us with a way to only synchronize a critical section, known as synchronized block.


public boolean withdrawMoney(int withdrawalAmount){

      // ...
      // other heavy non critical operations
      // ...
      synchronized (this) {
          if (balance >= withdrawalAmount) {
              provideTheAmountToCustomer(withdrawalAmount);
              balance = -withdrawalAmount;
              updateBalanceInDatabase(balance);
              return true;
          }
      }

      // ...
      // other heavy non critical operations
      // ...

      return false;
  }

Now, here we are synchronizing on this object, which means that any thread which is executing on this(current) object, will have to treat the synchronized block with respect - in other words it will have to acquire the lock on current object and then only proceed with the critical code.

Basically, each Object in java has a dedicated monitor (known as monitor or intrinsic lock), any thread entering a synchronized block/method, will check needs a hold of this lock , if it is already acquired by some other thread, the current thread will wait till it gets the lock. And a thread releases this lock, when it leaves the synchronized block/method.

So, the second way of using a synchronzied block will help us achieve thread safety as well as it will be performing better than synchronized methods.

Further, a if possible, we should try to make our fields final. And try to reduce class variables.

There are many other advanced ways of working with multithreaded applications, and keeping them thread safe, like  Locks, Reentrant locks, ThreadLocals, volatiles etc, which  I have tried to cover in next part of this article-chain.

Thanks

There are 4 sections of this Article:
Immutability and Thread Safety Part1- Covers Immutability
[This one] Immutability and Thread Safety Part2- Covers Thread Safety
Immutability and Thread Safety Part3- More on Thread Safety and Concurrency
Immutability and Thread Safety Part4 - Covers How the above 2 are related, and some further concepts