Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#
Article

Session locks in multi-threaded programming

Rate me:
Please Sign up or sign in to vote.
2.14/5 (7 votes)
29 Jan 2004CPOL4 min read 66.2K   623   10   6
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.

C#
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.

C#
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.

C#
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.

C#
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:

C#
// 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#.

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:

C#
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:

C#
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.

C#
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.

C#
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:

C#
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:

C#
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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I am a consultant, trainer, software archtect/engineer, since the early 1980s, working in the greater area of Boston, MA, USA.

My work comprises the entire spectrum of software, shrink-wrapped applications, IT client-server, systems and protocol related work, compilers and operating systems, and more ....

I am currently focused on platform development for distributed computing in service oriented data centers.

Comments and Discussions

 
QuestionHow can one contact Wytek Szymanski, the author of this arcticle? Pin
YossiMimon2-May-08 0:07
YossiMimon2-May-08 0:07 
Generalfile .zip Pin
luigi8122-Feb-04 8:15
luigi8122-Feb-04 8:15 
QuestionWhy not use this? Pin
e_invalidarg30-Jan-04 2:25
e_invalidarg30-Jan-04 2:25 
I'm not a Java developer but I would think in most cases you can design your classes/methods to avoid these problems. In your example you can easilly change the deposit & withdraw to return the current balance so you can write:

double newBalance = account.deposit(amount);

instead of:
account.deposit(amount);
double newBalance = account.getBalance();

Thanks,
Nikos.

==========

public class BankAccount {
private double _amount;

public synchronized double deposit(double amount) {
_amount += amount;
return _amount;
}

public synchronized double withdraw(double amount) {
_amount -= amount;
return _amount;
}

public synchronized double getBalance() {
return _amount;
}
}
AnswerRe: Why not use this? Pin
Andrew Phillips1-Feb-04 18:12
Andrew Phillips1-Feb-04 18:12 
GeneralMisleading. Pin
WREY30-Jan-04 2:09
WREY30-Jan-04 2:09 
GeneralRe: Misleading. Pin
PyjamaSam2-Feb-04 4:51
PyjamaSam2-Feb-04 4:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.