Session locks in multi-threaded programming






2.14/5 (7 votes)
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.