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);
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);
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();
account.deposit(1000.00);
double Balance = account.getBalance();
account.unlock();
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:
ArrayList list = ArrayList.Synchronized( new ArrayList() );
for(int i=0; i<20; i++)
list.Add( new BankAccount() );
int num = 0;
lock(list.SyncRoot)
{
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;
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;
}
}
}
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);
account.deposit(1000.00);
double Balance = account.Balance;
Monitor.Leave(account);
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);
}
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)
{
foreach(Object obj in list)
Monitor.Enter(obj);
double Sum = 0;
IEnumerator enm = list.GetEnumerator();
while(enm.MoveNext())
Sum += ((BankAccount)enm.Current).Balance;
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 {
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;
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.