Question

I wanted to see what happened if you change the reference of an object exclusively locked by Monitor.Enter(). As expected a SynchronizationLockException was thrown. But I was surprised to see several threads getting past the Monitor before the exception was thrown.

Here's what the code below is doing.

  1. create and start 100 threads
  2. make all the thread wait until a ManualResetEvent is set.
  3. set the ManualResetEvent - Kinda like waving the green flag at a Indy race.
  4. set a exclusive lock (Monitor.Enter(x)) on x
  5. change x's reference.

At this point I expected some sort of exception to be thrown, but that doesn't happen until Monitor.Exit(x). What's really odd is that 10 - 20 threads seem to be able to get past the lock before the exception is raised. How does that happen? Doesn't seem like it should. Of course changing the reference of the exclusively locked object is a no-no. I would never do it in real code, but still I was surprised to see the other threads getting past the monitor. Your thoughts?

using System;
using System.Threading;

namespace ThreadingPlayground
{
  class Program
  {
    private int _value;
    object x = new object();

    ManualResetEvent _event = new ManualResetEvent(false);

    static void Main()
    {
      Program p = new Program();
      p.StartThreads();
      Console.ReadLine();
    }

    private void StartThreads()
    {

      for(int i = 0;i<100;i++)
      {
        Thread t = new Thread(IncrementValue);
        t.Start();
      }
      _event.Set();
    }

    private void IncrementValue()
    {
      WaitHandle.WaitAll(new WaitHandle[] {_event});

      Monitor.Enter(x);

      // Change the reference of the exclusively locked object. This 
      // allows other threads to enter, should this be possible?
      x = Thread.CurrentThread.ManagedThreadId.ToString();

      Console.WriteLine(++_value);

      // throws a SynchronizationLockException 
      // but not until 10 - 20 more lines are written
      Monitor.Exit(x);
    }
  }
}

Console Output, looks like some threads got past the monitor??

Was it helpful?

Solution

What you're seeing is expected behavior. There is nothing special about the actual variable used to pass a reference into Monitor.Enter(). Changing the reference should not prevent other threads from acquiring an exclusive lock, as the variable has a new value, and that reference is not locked anywhere.

Your issue comes with the Exit, because the thread calling Exit does not have an exclusive lock on the reference being passed in. Another thread may well have a lock on it, but the thread you're executing in does not.

This, as you know, is why it's always best to do your locking with a variable whose reference will never change. If your resource's variable might change, use a new reference.

Is this clear enough?

OTHER TIPS

'x' is a reference to an object; it is not the object itself. When you do

      x = Thread.CurrentThread.ManagedThreadId.ToString();

You have thrown away the locked object that x previously referred to, and made x refer to some other object. Now when you do

      Monitor.Exit(x);

You get the exception because this object is not in fact locked - the object that you locked is now garbage to be collected by the garbage collector.

The reason for this behavior is that you're changing the value of x here:

x = Thread.CurrentThread.ManagedThreadId.ToString();

So when you get to

Monitor.Exit(x)

you're releasing the lock with a different object. It's as if you put a padlock with one key, and try to remove the padlock with the key from another padlock.

In addition, Console.Writeline is a costly instruction compared to the others, so several threads get to enter the Monitor before one of them gets near the finish line.

Here's an example run:

You start a bunch of threads.

  • Thread T1 acquires the lock with object x.
  • T2 tries to acquire lock with object x. This thread is out of luck, as we know it's going to wait forever.
  • T1 changes x. It's now a new object. I'll call it x'1.
  • T3 tries to acquire the lock. But the variable x actually references the object x'1. Nobody has locked on x'1, so he passes.
  • T3 changes x. It's now a new object called x'3.
  • T1 writes to the console.
  • T3 writes to the console.
  • T1 tries to release the lock with the variable x, which points to the object... x'3.
  • The Monitor object says: "Hey, what do you think you're doing? You don't have that lock! Eat this Exception little sucker"
  • Fin
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top