Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Session locks in multi-threaded programming

0.00/5 (No votes)
29 Jan 2004 1  
An article about synchronizing threads at a session level

Background

There is an article I have read once about Java's inability to synchronize at a session level. This was the case: Two worker threads hold a reference to a single BankAccount object. The BankAccount class is defined like so.

public class BankAccount {
  private double _amount;
  
  public synchronized void deposit(double amount) { 
    _amount += amount;
  }
  
  public synchronized void withdraw(double amount) { 
    _amount -= amount; 
  }
  
  public synchronized double getBalance() { 
    return _amount; 
  } 
}

One thread uses the object to deposit $1000 and requests the current balance immediately afterwards. The balance returned was surprisingly less than $1000. What happened is that the thread had switched between method calls, giving the other thread an opportunity to withdraw a sum of money.

account.deposit(1000.00);
// thread switches and the other thread withdraws some money

double Balance = account.getBalance();

There is no simple way in Java to prevent this from happening. You may think that the use of a synchronization block would be the answer.

synchronized(account) {
  account.deposit(1000.00);
  // thread switches but the other thread cannot withdraws money

  double Balance = account.getBalance(); 
}

This approach does not work because the other thread would block only if it passed through the same 'critical section' of code. It, however, passes through another section of code and therefore can still withdraw the money.

Java synchronization has no effect across multiple method calls. Java does not support the concept of a session lock, a locking mechanism that synchronizes across multiple method calls. This is how I would imagine it to work.

account.lock(); // lock this session across all threads

account.deposit(1000.00);
double Balance = account.getBalance();
account.unlock(); // unlock this session across all threads

You may refer to the book ['Java Threads', Scott Oaks & Henry Wong, O'REILLY, Chapter 3] to find out how much extra programming is needed to implement a session lock. Pages 53/53 describe a BusyFlag class.

Session Lock

A session lock is very useful especially when enumerating or iterating through all the elements in a collection. Fortunately, the .NET collection classes support the concept of a session lock. Here is an illustration:

// create a synchronized version of a collection object

ArrayList list = ArrayList.Synchronized( new ArrayList() );

// fill it with elements

for(int i=0; i<20; i++)
  list.Add( new BankAccount() );

// inside a thread procedure

int num = 0;
lock(list.SyncRoot) // this is a session lock

{
  double Sum=0;
  IEnumerator enm = list.GetEnumerator();
  while(enm.MoveNext())
    Sum += ((BankAccount)enm.Current).Balance;
    
  Console.Out.WriteLine("Sum: {0}", list.Count, num);
}

Every .NET collection class has this SynRoot member. If a lock is applied on this member, then every other thread is prevented from modifying the collection. No elements inside the collection can be removed or even modified. And no elements can be added to that collection. The SyncRoot is really a very good feature. But it is only available with the collection classes.

Nevertheless, we can provide session lock capabilities to any class of our own design with just a few lines of code. Unlike Java, the .NET framework provides us with a Monitor object which is a kind of critical section or mutex. Let us rewrite the BankAccount class in C#.

public class BankAccount {
  private double _amount;
  
  // the account methods

  public void deposit(double amount) {
    lock(this) {
      _amount += amount;
    }
  }
  public void withdraw(double amount) {
    lock(this) {
      _amount -= amount;
    }
  }
  public double Balance {
    get {
      lock(this) {
        return _amount;
      }
    }
  }
  
  
  // here are the session lock methods

  public lock() {
    Monitor.Enter(this);
  }
  public unlock() {
    Monitor.Exit();
  }
}

That is all. Thanks to the Monitor, we can lock the bank account across multiple method calls just as we have demonstrated above with the Java code.

The .NET Monitor is not really a critical section or mutex type of object. It in fact holds a lock on an object and monitors it across multiple threads. Until the lock is released, we can call multiple methods without the interference of another thread. Note, it does not prevent a thread switch but the other thread will be blocked from interfering.

If you look carefully at how I have used it in the BankAccount class, then you can readily see that the following code is equivalent:

Monitor.Enter(account); 
// equivalent to account.lock() { Monitor.Enter(this); }

account.deposit(1000.00); 
double Balance = account.Balance;
Monitor.Leave(account); 
// equivalent to account.unlock() { Monitor.Exit(this); }

FYI: The C# keyword lock is just a C# thing. The CLR does not understand it. The C# compiler translates lock to something like this:

try
{
  Monitor.Enter(account);
  // put your code here

}
finally
{
  Monitor.Exit(account);
}

The good news here is that we can apply a session lock in the more familiar way.

lock(account) {
  account.deposit(1000.00); 
  double Balance = account.Balance;
}

When writing multi-threaded programs, it is helpful to keep the Monitor in mind. For example, you may want to calculate the total sum of money in a list of bank accounts. Applying a lock on the SyncRoot before iterating through the list and adding the balances of each account would only work if no BankAccount object was referenced outside the list. To prevent a change to any of the bank accounts, you may want to use the Monitor.

lock(list.SyncRoot)
{
  // lock each object in the list

  foreach(Object obj in list)
    Monitor.Enter(obj);
   
  // compute the total sum  

  double Sum = 0;
  IEnumerator enm = list.GetEnumerator();
  while(enm.MoveNext())
    Sum += ((BankAccount)enm.Current).Balance;
  
  // unlock each object in the list

  foreach(Object obj in list)
    Monitor.Exit(obj);
}

When writing multi-threaded applications, it is important to distinguish between critical section type and monitor type of synchronizations. The .NET Monitor object is not the same as a common mutex or critical section. But you can use the Monitor to achieve a critical section kind of effect. Here is an example:

public class BankAccount {

  // ...

  
  // synchronizing on a private object achieves

  // a critical section kind of effect

  private Object balance = new Object();
  public double Balance {
    get {
      lock(balance) {
        return _amount;
      }
    }
  }
}

The trick is to reserve a private member as a synchronization reference. In general, you should design your synchronization around each data member of your class. Because the BankAccount is a small class, we have taken the object itself as a synchronization reference. If the class was more complex, a better approach would be like so:

public class BankAccount {
  private double _amount;

  // just to be the synchronization reference for '_amount'

  public Object Amount = new Object(); 
 
  public void deposit(double amount) {
    lock(Amount) {
      _amount += amount;
    }
  }
  
  public void withdraw(double amount) {
    lock(Amount) {
      _amount -= amount;
    }
  }
  
  // ....

}

Now we just need to lock the synchronization reference, Amount.

lock(account.Amount) {
    account.deposit();
    double Balance = account.Balance;
}

The attached project

You may download the attached project and play around with it. It is a simple console app that demonstrates the effect of a session lock. Just comment certain lines in or out to see the difference. I have forced a thread switch with Thread.Sleep(100) in various places to achieve the desired effects.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here