BYTE-force columns
Company news, team and friends.

Double-Checked Locking

«XOR's Post»

juick.com/xor

Loading...

Headline

Стремясь к оптимизации выполнения программы, многие люди независимо приходят к одинаковому решению для ускорения «ленивой» инициализации членов класса в многопоточной среде. Приведем следущий пример (который, как выясняется, не совсем корректен):

class Singleton 
{
    private static readonly object _locker = new object();
    private static Singleton _instance = null;

    public static Singleton Instance
    {
	get
	{
	    if( _instance == null ) 
	    {
                lock( _locker ) 
                    if( _instance == null ) 
                        _instance = new Singleton();
            }
            return _instance;      
        }
    }
}

Логика кода совершенно очевидна. Если _instance == null, то мы запираем доступ для других тредов и еще раз проверяем _instance на null, дабы убедиться, что за время, прошедшее между проверкой условия и запиранием, другой поток не успел проинициализировать поле.

Казалось бы, что ошибиться здесь нельзя, но, как оказывается, в Ява-мире подобный подход уже давно разрбран по полочкам и признан недееспособным. Причины подробно разъясняются в широко цитируемой статье "Double-Checked Locking is Broken". Вкратце, все упирается в проблемы, которые возникают при работе оптимизирущего компилятора, а также в многопроцессорных системах.

Microsoft говорит, что им была известна эта проблема, поэтому в .NET есть способ ее корректного решения – нужно использовать ключевое слово volatile. (Замечу, что пример, приведенный в статье несколько неправилен. Там используется lock(typeof), что они сами признали плохой практикой).

class Singleton 
{
    private static readonly object _locker = new object();
    private static volatile Singleton _instance = null;

    public static Singleton Instance
    {
	get
	{
	    if( _instance == null ) 
	    {
                lock( _locker ) 
                    if( _instance == null ) 
                        _instance = new Singleton();
            }
            return _instance;      
        }
    }
}

Есть также и другие, менее экзотические средства:

  1. Можно всегда сначала запирать, а потом проверять на null. Этот метод гарантированно работает, хотя из-за постоянного запирания он должен быть чуть-чуть медленнее.
  2. Можно проинициализировать поле _instance прямо в декларации класса. В этом случае реальная инициализация произойдет где-то перед первым обращением к полю, т.е. вся нужная работа будет сделана вообще без нашего участия.

Posted Jan 25 2006, 01:54 PM by Andrew Mayorov
Filed under: ,
Copyright ©2004-2009 BYTE-force
Powered by Community Server (Non-Commercial Edition), by Telligent Systems