Threading: Class locks vs Object locks




I have to go back to my university days to remember the first multi threaded programs that I wrote.  I even took entire subjects devoted to Threading in .NET, in which we created, used and understood a vast range of threading utitlities such as Mutex, Semaphore, Bounded Channel, Exchanger, Latch, Read/Write Lock and Rendevous.  Working with multi threaded programs has always been a challenge as testing and debugging becomes very difficult, and in some cases extremely frustrating and almost impossible  (anyone that has experienced the debugger during context switches will know what I'm talking about).  In these cases you have to 'mind meld' (to borrow the phrase from my Threading in .NET lecturer at uni) with your program to work through the threading issues.

The truth is I have rarely had to ever use any of these utilities in my job, and 99% of the time when I write a threaded program, the inbuilt .NET 'lock' keyword does the job.  One of the more common things that pops up however is the distinction between 'locking the class' and 'locking the object'.

'Class Lock' should be used when you want to ensure a particular method can only be accessed by one thread for ALL objects of that class type at any one time.  This is achieved by acquiring a lock on a static object.

'Object Lock' should be used when you want to ensure a particular method can only be accessed by one thread for that SINGLE object at any one time.  This is achieved by acquring a lock on an instance object.

So put simply: Class lock will affect all objects of that type, Object Lock will affect only that particular instance of the class.

If the above explanation made no sense at all (I still get confused by this - so there is a pretty good chance that could be the case), then I'll explain below with some examples.


Lock the CLASS.

    public class ThreadSafeClass
    {
         private static object _SyncLock = new object();

         public void DoSomethingImportant()
         {
              lock(_SyncLock)
              {
                   //important stuff that can only be accessed by one thread at a time
                   //... for ALL objects of this class type
              }
         }
    }


Lock the OBJECT

    public class ThreadSafeClass
    {
         private object _SyncLock = new object();

         public void DoSomethingImportant()
         {
              lock(_SyncLock)
              {
                   //important stuff that can only be accessed by one thread at a time
                   //... for ALL objects of this class type
              }
         }
    }


Look pretty similar don't they?  They are, except for one small but extremely important difference.  When using Class Lock, we are acquiring a 'static' sync lock object.  This will mean that as soon as a thread acquires this lock, all other threads will be blocked from accessing this method on ALL of the objects of this type.  ie. If I had 6 BusinessClass objects, the 'DoSomethingImportant()' method could only be accessed in one of the objects at a time.

On the contrary - in the case of the Object Lock - the sync lock is a member of the object, therefore acquiring the lock for one of the objects has no affect on any of the other objects.  This means the 'DoSomethingImportant()' method could be accessed by multiple threads in each of the objects all at the same time.

To prove this I created a little testing app below:

    class TestProgram
    {
        static void Main(string[] args)
        {

            ThreadSafeClass c1 = new ThreadSafeClass();
            Thread t1 = new Thread(new ThreadStart(c1.DoSomethingImportant));
            t1.Name = "t1";

            ThreadSafeClass c2 = new ThreadSafeClass();
            Thread t2 = new Thread(new ThreadStart(c2.DoSomethingImportant));
            t2.Name = "t2";

            ThreadSafeClass c3 = newThreadSafeClass();
            Thread t3 = new Thread(new ThreadStart(c3.DoSomethingImportant));
            t3.Name = "t3";

            t1.Start();
            t2.Start();
            t3.Start();

            t1.Join();
            t2.Join();
            t3.Join();

            Console.WriteLine("done");
            Console.ReadLine();
        }
    }


I changed the body of DoSomethingImporant() to:

        public void DoSomethingImportant()
        {
            lock (_SyncLock)
            {
                Console.WriteLine(DateTime.Now + " doing something: " + Thread.CurrentThread.Name);
                Thread.Sleep(5000);
                Console.WriteLine(DateTime.Now + " finished: " + Thread.CurrentThread.Name);
            }
        }


Results for both locks below:

Class Lock:

9/11/2007 8:04:33 AM doing something: t1
9/11/2007 8:04:38 AM finished: t1
9/11/2007 8:04:38 AM doing something: t2
9/11/2007 8:04:43 AM finished: t2
9/11/2007 8:04:43 AM doing something: t3
9/11/2007 8:04:48 AM finished: t3
done

Object Lock:

9/11/2007 8:03:45 AM doing something: t2
9/11/2007 8:03:45 AM doing something: t3
9/11/2007 8:03:45 AM doing something: t1
9/11/2007 8:03:50 AM finished: t2
9/11/2007 8:03:50 AM finished: t1
9/11/2007 8:03:50 AM finished: t3
done


As you can see, the Class Lock allowed only one thread at a time for ALL of its objects - i.e. the threads had to wait in queue.   Object lock allowed ALL threads in at the same time, on all the individual objects.

So after all that, you may be thinking: in what circumstances would you use each lock? 

Object Lock: Use this when the operation is independent for each object (of that class type) - meaning it doesn't have any affect on any of the other objects of that type.  Eg.  In a 'BankAccount' object, you may have a 'Credit()' method.  This method can be called, and you want to ensure that it can only be accessed by one thread at a time, but only for that particular object.  It doesn't matter if there are 100 BankAccounts and they are all credited at the exact same time.

Class Lock:  Use this when the operation will affect all objects of that class type.  This may be something like 'GetNextId()' - where the 'id' it is getting must be unique across all objects.  Therefore we want to ensure only one object of that type can access the 'GetNextId()' method at a time.

Still confused? Hopefully not.  If you are, leave me a comment and tell me that I've gone crazy.  If not, hopefully I've helped you out if you were stuck in another one of those annoying threading problems.



 del.icio.us  Stumbleupon  Technorati  Digg 

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this entry.
Comments

Leave a comment

 Enter the above security code (required)

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.