Modern processors support atomic (in other words thread-safe) operations on numbers
for certain operations. These operations include increment, decrement, comparison,
exchange and comparison and exchange. .NET exposes these atomic operations
using the Interlocked class. For example if you used delay-initialization
to avoid creating a field value until you needed it and the class had to be thread-safe
then you could implement it like this.
private SomeClass m_Data;
public SomeClass Data
{
get
{
if (m_Data == null)
m_Data = new SomeClass();
return m_Data;
}
}
The above code checks to see if the object had been created yet (not thread-safe).
If it has not then it is created and assigned to a private field (not thread-safe).
The field is then returned. Assignment itself is atomic for reference types
but nothing guarantees that between the check in the if statement
and the assignment that someone else did not come along and initialize the data.
Here is a thread-safe version.
private SomeClass m_Data;
private object m_lckData = new object();
public SomeClass Data
{
get
{
if (m_Data == null)
{
lock(m_lckData)
{
if (m_Data == null)
m_Data = new SomeClass();
}
};
return m_Data;
}
}
Not too bad but a little long and it requires that you define a synchronization
object or use lock. Notice that this method uses a technique
known as double checking. The first check is designed to catch the normal
case of the object being created or not. If it has not been created yet then
we take the lock (which impacts performance) and then check again. We have
to check again because between the time our if statement ran and
now someone else might have initialized the object. So in the normal case
we execute 1 if statement and take no locks. In the initial
case we will execute 2 if statements and a lock. Here is
an even better version.
private SomeClass m_Data;
public SomeClass Data
{
get
{
if (m_Data == null)
Interlocked.CompareExchange(ref m_Data, new SomeClass(),
null);
return m_Data;
}
}
This code is a lot cleaner and does not require any extra fields. It has the
same effect. We first check to see if the object has been created yet.
If it hasn't then we will create a new instance. However we then atomically
check to see if the data is still null. If it isn't then we assign the new
instance to the field. The only real difference between this code block and
the last is that this code block might create a new instance of the field class
before it realizes it is not needed. In general I find this to be a minor inconvenience.