When to use lock(this) and when not to use lock(this)

  • Before we see when to use and not use lock(this).
  • First let's see when to use locks
  • For explaining refer the below given code
    class JointAccount
    {
        int Balance = 10000;     
        public void WithDraw(int Amount)
        {
          
                if (Amount <= Balance)
                {
                    Console.WriteLine("Balance before withdraw: {0}", Balance);
                    Balance = Balance - Amount;
                    Console.WriteLine("Balance after withdraw: {0}", Balance);
                }
                else
                {
                    Console.WriteLine("Cannot withdraw, as amount exceeds balance");
                }          
        }
    }

    class Program
    {    
        static void Main(string[] args)
        {
            JointAccount jointAccount = new JointAccount(); //Joint Account of 10 members

            for (int i = 0; i < 10; i++)
            {                
                new Thread(() => jointAccount.WithDraw(10000)).Start();
            }
            Console.ReadLine();
        }
    }
  • It is Joint Account, so any one can have access to the account can withdraw amount from the Joint Account.
  • As per the above example Single Instance of JointAccount class is accessed by 10 threads almost at the same time
  • But anyhow, the balance shouldn't go negative, they cannot withdraw the money equal to the balance that they have in their account
  • But the above example will allow to violate the above said condition
  • Take Note: In a multi-threaded environment you cannot expect the same result all the times like single threaded, because of some times it may produce correct result, that doesn't mean it will always, so run your program multiple time to confirm you code is working fine.
  •  So the above code result may be like this
Balance before withdraw: 10000
Balance after withdraw: 0
Balance before withdraw: 10000
Balance after withdraw: -10000
Balance before withdraw: 10000
Balance after withdraw: -20000
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
  • So here, in this situation we need locking, because the code allows for balance to go negative
  • So now, that we all know a variable can be used as a locker, it should be of reference type.
  • And the locker variable should be private member of that class.
  • Why it should be a private member, why does it cannot be public?
  • A locker object should refer to the same object in memory, when it receives call from multiple threads
  •  So that it will be considered as all the threads are try to work on the same object(resource)
  • Now let's add the lock using a string object with public access modifier
class JointAccount
    {
        int Balance = 10000;
        public string locker = "";        
        public void WithDraw(int Amount)
        {
            lock (locker)
            {

                if (Amount <= Balance)
                {
                    Console.WriteLine("Balance before withdraw: {0}", Balance);
                    Balance = Balance - Amount;
                    Console.WriteLine("Balance after withdraw: {0}", Balance);
                }
                else
                {
                    Console.WriteLine("Cannot withdraw, as amount exceeds balance");
                }
            }
        }
    }
Balance before withdraw: 10000
Balance after withdraw: 0

Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
  • Now I have added the lock using a string object with access modifier public and it Works Fine. What is the issue?
  • let's modify the code, to change the locker reference before each thread request and see what happens.
class Program
    {    
        static void Main(string[] args)
        {
            JointAccount jointAccount = new JointAccount(); //Joint Account of 10 members

            for (int i = 0; i < 10; i++)
            {
                jointAccount.locker = i.ToString();
                new Thread(() => jointAccount.WithDraw(10000)).Start();
            }
            Console.ReadLine();
        }      
    }
Balance before withdraw: 10000
Balance before withdraw: 10000
Balance after withdraw: -10000
Balance after withdraw: 0

Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
  • After the changee, it may produce result like this, please read the Note In a multi-threaded environment you cannot expect the same result all the times like single threaded
  • There are chances, that the public member reference may change, that is why it is advisable to use private member.
  • Now lets come to the point When to use lock(this)?
  • In the prior example all the threads are working on a single instance of class. That is the Balance is commonly shared by all the threads. So here we can use lock(this) instead of having a separate locking object.
class JointAccount
    {
        int Balance = 10000;
        public void WithDraw(int Amount)
        {
            lock (this)
            {

                if (Amount <= Balance)
                {
                    Console.WriteLine("Balance before withdraw: {0}", Balance);
                    Balance = Balance - Amount;
                    Console.WriteLine("Balance after withdraw: {0}", Balance);
                }
                else
                {
                    Console.WriteLine("Cannot withdraw, as amount exceeds balance");
                }
            }
        }
        public void displayBalance()
        {
            if(Balance<0)
            Console.WriteLine("Final Balance {0}", Balance);
        }
    }

class Program
    {     
        static void Main(string[] args)
        {                      
                JointAccount jointAccount = new JointAccount(); //Joint Account of 10 members
                for (int i = 0; i < 10; i++)
                {                    
                    new Thread(() => jointAccount.WithDraw(10000)).Start();
                }                          
            Console.ReadLine();
        }       
    }

Balance before withdraw: 10000
Balance after withdraw: 0

Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
Cannot withdraw, as amount exceeds balance
  • When not to use lock(this) ?
  • Refer the example below
    class ThreadUnsafe {
        static int _val1 = 1, _val2 = 1;
        public void Go() {          
                if (_val2 != 0)
                    Console.WriteLine(_val1 / _val2);
                _val2 = 0;           
        }
    }
   class Program
    {     
        static void Main(string[] args)
        {
            Thread[] threads = new Thread[20];
           for (int i = 0; i < 20; i++)
           {
                threads[i] = new Thread(() => new ThreadUnsafe().Go());                
            }
            for (int i = 0; i < 20; i++)
            {
                threads[i].Start();
            }
           Console.ReadLine();
        }       
    }
Answer is 1
Answer is 1
  • As you can see in the above example new instance of the class ThreadUnsafe is created with to call the Go method to update the static variable _val2.
  • But more than one thread updated the value of _val2. Even chances are there to get
    DivideByZeroException because one thread updated _val2 to 0 and other thread performing _val1 / _val2.
  •  So to make it thread safe, if updated this code with lock(this)
class ThreadUnsafe {
        static int _val1 = 1, _val2 = 1;      
        public void Go() {
            lock (this)
            {

                if (_val2 != 0)
                    Console.WriteLine("Answer is {0}", _val1 / _val2);
                _val2 = 0;
            }
        }
    }

Answer is 1
Answer is 1
Answer is 1
  • But still the code produces same kind of result
  • Why this happens?
  •  Ok, lets update the code with instance level locker instead of lock(this) and see what happens
class ThreadUnsafe {
        static int _val1 = 1, _val2 = 1;
        object locker = new object();
        public void Go() {
            lock (locker)
            {

                if (_val2 != 0)
                    Console.WriteLine("Answer is {0}", _val1 / _val2);
                _val2 = 0;
            }
        }
    }

Answer is 1
Answer is 1
Answer is 1 
  • From this we can understand one thing lock(this) and instance level locker is the same because of both work at the instance level, so each thread have its own object of locker/it is own object.
  • So here it should not be be instance level locker, it should be a class level locker
  • Now it works 
class ThreadUnsafe {
        static int _val1 = 1, _val2 = 1;
        static object locker = new object();
        public void Go() {
            lock (locker)
            {
                if (_val2 != 0)
                    Console.WriteLine("Answer is {0}", _val1 / _val2);
                _val2 = 0;
            }
        }
    }

Answer is 1

No comments:

Post a Comment

Framework Fundamentals - String - Comparing Strings

In comparing two values, the .NET Framework differentiates the concepts of equality comparison and order comparison . Equality compariso...