It is bad form to use this
in lock statements because it is generally out of your control who else might be locking on that object.
In order to properly plan parallel operations, special care should be taken to consider possible deadlock situations, and having an unknown number of lock entry points hinders this. For example, any one with a reference to the object can lock on it without the object designer/creator knowing about it. This increases the complexity of multi-threaded solutions and might affect their correctness.
A private field is usually a better option as the complier will enforce access restrictions to it, and it will encapsulate the locking mechanism. Using this
violates encapsulation by exposing part of your locking implementation to the public. It is also not clear that you will be acquiring a lock on this
unless it has been documented. Even then, relying on documentation to prevent a problem is sub-optimal.
Finally, there is the common misconception that lock(this)
actually modifies the object passed as a parameter, and in some way makes it read-only or inaccessible. This is false. The object passed as a parameter to lock
merely serves a key. If a lock is already being held on that key, the lock cannot be made, otherwise, the lock is allowed.
Run the following C# code as an example.
public class Person
{
private int age;
private string name;
public int Age
{
get { return age; }
set { age = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public Person(string name)
{
this.age = 0;
this.name = name;
}
public void LockThis()
{
lock (this)
{
System.Threading.Thread.Sleep(10000);
}
}
public void GrowOld()
{
this.age++;
}
}
class Program
{
static void Main(string[] args)
{
Person nancy = new Person("Nancy Drew");
nancy.Age = 15;
Thread a = new Thread(nancy.LockThis);
Thread b = new Thread(Program.Timewarp);
Thread c = new Thread(Program.NameChange);
a.Start();
b.Start(nancy);
c.Start(nancy);
a.Join();
Console.ReadLine();
}
static void Timewarp(object subject)
{
Person person = subject as Person;
if (person == null) throw new ArgumentNullException("subject");
lock (person.Name)
{
while (person.Age <= 23)
{
if (Monitor.TryEnter(person, 10) == false)
{
Console.WriteLine("'this' person is locked!");
}
else Monitor.Exit(person);
person.GrowOld();
Console.WriteLine("{0} is {1} years old.", person.Name, person.Age);
}
}
}
static void NameChange(object subject)
{
Person person = subject as Person;
if (person == null) throw new ArgumentNullException("subject");
// Be careful about using strings as locks
if (Monitor.TryEnter(person.Name, 30) == false)
{
Console.WriteLine("Failed to obtain lock on 'person.Name' on first try, wait longer.");
}
else Monitor.Exit(person.Name);
if (Monitor.TryEnter("Nancy Drew", 30) == false)
{
Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same thanks to inlining!");
}
else Monitor.Exit("Nancy Drew");
if (Monitor.TryEnter(person.Name, 10000))
{
string oldName = person.Name;
person.Name = "Nancy Callahan";
Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name);
}
else Monitor.Exit(person.Name);
}
}