Click here to Skip to main content
15,879,474 members
Articles / Mobile Apps
Article

Synchronization Basics and Concept of Using a Synchronized Wrapper

Rate me:
Please Sign up or sign in to vote.
2.69/5 (6 votes)
21 Feb 20056 min read 53.1K   352   19   9
To prevent concurrent access to a data structure, one way is to use thread-aware objects, the other is to make a thread-safe wrapper for the object.

Introduction

Multithreaded applications provide the illusion that numerous activities are happening at more or less the same time. In C#, the System.Threading namespace provides a number of types that enable multithreaded programming.

Concurrency is one of the key concepts when more than one thread accesses a shared data. Uncontrolled concurrent access to the object may either leave it in an indeterminate state, which leads to runtime exceptions, or the object will behave unexpectedly and generate random, garbage output.

In three occasions, a shared object does not need synchronization.

  • If the object is written but never read.
  • If the object is read but never written.
  • If only a single thread accesses the object at any given time.

An object that does not fall into one of these conditions (and most of real objects will not, for God's sake, who wants to have an object that he writes but never reads and vice versa) should to be thoroughly analyzed and properly synchronized if it is to be used, in a multithreaded environment.

Purpose of this Article

Let us assume that you have designed a thread-silly object which is supposed to run in a single-threaded environment. Then all of a sudden, your project specs have changed (remember that customers are -and your boss is- always right :)). Now your poor innocent object has found itself unprotected in a multi-threaded environment.

This article discusses what may happen to a thread-silly object in a multi-threaded environment and proposes a method (e.g. using a synchronized wrapper) for safely using it in this multi-threaded environment without changing its internal structure too much.

When to use Synchronization?

Synchronizing an object is done via Monitors and monitoring brings an overhead to the application. So if your object will not be executed in a multi-threaded environment, using thread-unaware objects will be more efficient than their thread-aware counterparts.

I would like to expand the concept of thread-awareness a bit:

Thread-Awareness and Thread-Safety

Most of the time people use thread-aware/thread-safe words interchangeably. However, there is a slight difference between them. Despite my thorough Googling on the topic, I was unable to find a clear written definition. People appear to have their own subjective interpretations. After reading here and there and everywhere for definitions on the subject, here follows my boiled up definition for them: (I am open to and will appreciate any contributions on the terms)

Thread aware:

At any given time, at most one thread can be active on the object. The object is aware of the threads around it and protects itself from the threads by putting all the threads in a queue. Since there can be only a single thread active on the object at any given time, the object will always preserve its state. There will not be any synchronization problems.

Thread safe:

At a given time, multiple threads can be active on the object. The object knows how to deal with them. It has properly synchronized access to its shared resources. It can preserve its state data in this multi-threaded environment (i.e. it will not fall into intermediate and/or indeterminate states). It is safe to use this object in a multi-threaded environment.

Using an object that is neither thread-aware nor thread-safe may result in getting incorrect and random data and mysterious exceptions (due to trying to access the object when it is being used by a thread and is in an unstable, in-between state at the instant of access of the second thread).

A Simple Interface

Let us begin by creating a simple interface:

C#
namespace com.sarmal.articles.en.synchronization
{
    using System;
    public interface BankAccount 
    {
        void Empty();
        void Add(double money);
        double Balance {get;}
        bool IsSynchronized {get;}
        object SyncRoot {get;}
    }
}

Not a big surprise huh? Empty() method clears the balance, Add(double) adds money to the bank account, and Balance is the total amount of money currently deposited in the account.

The two other elements that require more attention are IsSynchronized and SyncRoot. IsSynchronized method returns whether the object is safe for multi-threaded access, and SyncRoot is the synchronization root of the object which you can pass to lock statement as a parameter.

C#
lock(acc.SyncRoot) {
    ... critical code goes here ...
}

Implementation Class

The implementation of the interface is not a big issue. There is a private double member variable, Add method adds to it, Empty sets it to zero, and Balance returns what is currently stored in that variable.

One thing that is noteworthy is the Add method:

C#
public virtual void Add(double money) {
    double temp = sum;
    Thread.Sleep(0);
    temp += money;
    sum = temp;
}

Note that those four lines of code is equivalent to nothing but sum += money. We have split the statements into lines and added a Thread sleep in between to increase the probability of getting concurrency-related errors. Add(double money) {sum+=money;} can also serve the purpose of this article, however the code presented above leads to a more dramatic and distinct outcome.

The Overridden Synchronized Methods

Things that take attention in the implementation class AccountImpl are the two overridden Synchronized methods:

C#
public static BankAccountImpl Synchronized(BankAccountImpl impl) {
    return (BankAccountImpl) Wrap(impl);
}

public static BankAccount Synchronized(BankAccount acc) {
    return (BankAccount) Wrap(acc);
}

The private method Wrap returns the passed BankAccount object itself if the parameter is synchronized, otherwise it returns a synchronized wrapper class SyncAccount which extends BankAccountImpl.

The wrapper class SyncAccount, which is the key point of this discussion, is a private sealed inner class. It stores the reference of the BankAccount as a private member and locks critical portions of the code using the SyncRoot of the member.

C#
private sealed class SyncAccount:BankAccountImpl {
    private object syncRoot;
    private BankAccount bankAccount;

    public SyncAccount(BankAccount acc) {
        bankAccount = acc;
        syncRoot = acc.SyncRoot;
    }
    
    public override void Empty() {
        lock(syncRoot) {
            bankAccount.Empty();
        }
    }
    
    ... truncated ...
    
    public override bool IsSynchronized {get {return true;}}
    public override object SyncRoot {get {return syncRoot;}}
}

The Test Case

Test.cs includes the Test class to test the application. If you open it, you will see a commented out code in its main method.

C#
acc = new BankAccountImpl();
//acc = BankAccountImpl.Synchronized(acc);

When you build the project and run, it will generate an output similar to the following:

Total balance is expected to be:  120.
Starting Thread-0
Starting Thread-1
Starting Thread-2
Thread-0 entered add.
Thread-0: Balance before add : 0

... truncated ...

Starting Thread-4
Thread-0: Balance after add : 4
Thread-0: Balance before add : 4
Thread-1: Balance after add : 4
Thread-1: Balance before add : 4
Thread-0: Balance after add : 5
Thread-0: Balance before add : 5
Thread-1: Balance after add : 5
Thread-1: Balance before add : 5

... truncated ...

Thread-9: Balance after add : 30
Thread-9 exited add.
Thread-11: Balance after add : 30
Thread-11: Balance before add : 30
Joining Thread-10
Joining Thread-11
Thread-11: Balance after add : 31
Thread-11 exited add.
The current balance is 31.

The outcome will be different for each run but the trend will be the same. Current balance will always be less than the expected balance.

The situation may be seen somewhat analogous to the good old producer-consumer dilemma (in reverse). At a given instant of time, more than one thread time read (i.e. consumed) the shared variable. The threads increment the value they had read (i.e., not the original value but the snapshot they took) by one and store it back (i.e., produced).

Now let us uncomment the commented out parts, and rebuild the solution.

C#
acc = new BankAccountImpl();
acc = BankAccountImpl.Synchronized(acc);

We will get a synchronized wrapper around the class, and everything will be as expected. Only one thread will be able to access each method at any given time, which will ensure data integrity.

Here is what the outcome will look like after rebuild:

Total balance is expected to be:  120.
Starting Thread-0
Starting Thread-1
Starting Thread-2
Thread-1 entered add.
Thread-1: Balance before add : 0
Thread-1: Balance after add : 1
Thread-1: Balance before add : 1
Starting Thread-3
Thread-1: Balance after add : 2
Thread-1: Balance before add : 2
Thread-1: Balance after add : 3
Thread-1: Balance before add : 3
Thread-0 entered add.
Thread-2 entered add.
Thread-0: Balance before add : 4
Thread-0: Balance after add : 5
Thread-0: Balance before add : 5
Starting Thread-4
Thread-0: Balance after add : 6
Thread-0: Balance before add : 6
Starting Thread-5
Thread-0: Balance after add : 7
Thread-0: Balance before add : 7

... truncated ...

Joining Thread-5
Joining Thread-6
Joining Thread-7
Joining Thread-8
Joining Thread-9
Joining Thread-10
Joining Thread-11
The current balance is 120.

Conclusion

Synchronization is an important factor to preserve data concurrency in a multithreaded environment. No matter in which language you code, whether it is ANSI C or C# or Java or anything else, if there are multiple processes that rush to gain control of a shared resource, careful analysis of the situation is extremely necessary. Real-world multi-threaded scenarios are not as simple as the above Account example. Yet the .NET framework makes the threading and synchronization easy to handle. To be honest, IMHO, the threading capabilities of C# beats Java.

So What's Next ?

Rather than diving threading stuff in great detail, I preferred to discuss basic issues around a sample application. I spare a detailed examination of System.Threading namespace's methods, advanced issues such as caching, memory models, memory barriers, lazy initialization, and conceptual topics such as deadlocks, atomicity, thread-safety, race conditions, semaphores, mutexes, critical sections etc. etc... to my proceeding articles. Else I would be rather boring and would be consuming too much paper space.

Happy coding!

History

  • 2005-02-21
    • Article created.
  • 2005-02-24
    • Added some descriptive text, modified the code (added some extra console prints to describe what's going on.)
    • Created XML documentation
    • Uploaded the revised demo project.
  • 2005-02-26
    • Modified the article according to the revised code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Turkey Turkey
Volkan is a java enterprise architect who left his full-time senior developer position to venture his ideas and dreams. He codes C# as a hobby, trying to combine the .Net concept with his Java and J2EE know-how. He also works as a freelance web application developer/designer.

Volkan is especially interested in database oriented content management systems, web design and development, web standards, usability and accessibility.

He was born on May '79. He has graduated from one of the most reputable universities of his country (i.e. Bogazici University) in 2003 as a Communication Engineer. He also has earned his Master of Business Administration degree from a second university in 2006.

Comments and Discussions

 
QuestionWhat is with link to sources Pin
21-Feb-05 21:34
suss21-Feb-05 21:34 
AnswerRe: What is with link to sources Pin
volkan.ozcelik22-Feb-05 0:34
volkan.ozcelik22-Feb-05 0:34 
GeneralRe: What is with link to sources Pin
volkan.ozcelik22-Feb-05 1:04
volkan.ozcelik22-Feb-05 1:04 
GeneralRe: What is with link to sources Pin
hsd9922-Feb-05 20:10
hsd9922-Feb-05 20:10 
GeneralRe: What is with link to sources Pin
volkan.ozcelik22-Feb-05 20:45
volkan.ozcelik22-Feb-05 20:45 
GeneralThe download works okay now. Pin
volkan.ozcelik22-Feb-05 22:29
volkan.ozcelik22-Feb-05 22:29 
GeneralRe: The download works okay now. Pin
23-Feb-05 21:10
suss23-Feb-05 21:10 
AnswerRe: What is with link to sources Pin
Almighty Bob23-Feb-05 8:00
Almighty Bob23-Feb-05 8:00 
GeneralRe: What is with link to sources Pin
volkan.ozcelik25-Feb-05 4:44
volkan.ozcelik25-Feb-05 4:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.