简体   繁体   中英

Are static variables thread-safe? C#

I want to create a class which stores DataTables, this will prevent my application to import a list of details each time I want to retrieve it. Therefore this should be done once, I believe that the following code does so, but I am not sure if it is thread-safe.

The below code is in the Business Layer Section of my three tier application, it is returning a DataTable to the Presentation Layer.

public class BusinessLayerHandler
{
    public static DataTable unitTable;
    public static DataTable currencyTable;

    public static DataTable GetUnitList()
    {
        //import lists each time the application is run
        unitTable = null;
        if (unitTable == null)
        {
            return unitTable = DatabaseHandler.GetUnitList();
        }
        else
        {
            return unitTable;
        }
    }

    public static DataTable GetCurrencyList()
    {
        //import lists each time the application is run
        currencyTable = null;
        if (currencyTable == null)
        {
            return currencyTable = DatabaseHandler.GetCurrencyList();
        }
        else
        {
            return currencyTable;
        }
    }

Any help is appreciated, if there is a better way how to cache a DataTable please let me know.

Update:

Thanks to your opinions, this is the suggested method to do it, if I understood correctly:

public class BusinessLayerHandler
{
    private static DataTable unitTable;
    private static DataTable currencyTable;

    private static readonly object unitTableLock = new object();
    private static readonly object currencyTableLock = new object();

    public static DataTable GetUnitList()
    {
        //import lists each time the application is run
        //unitTable = null;

        lock (unitTableLock)
        {
            if (unitTable == null)   
            {
                return unitTable = DatabaseHandler.GetUnitList();
            }
        }
        return unitTable;
    }

    public static DataTable GetCurrencyList()
    {
        //import lists each time the application is run
        lock (currencyTableLock)
        {
            if (currencyTable == null)
            {
                return currencyTable = DatabaseHandler.GetCurrencyList();
            }
        }
        return currencyTable;
    }
}

It appears as though all you want to do is load it once and keep a reference to it. All you need to guard is initialising the variable if it's null. Null checking, locking and null checking again is called Double Check Locking and will work well for you. It's best practice to provide a separate locking object, so you have good control over granularity of locks.

Note this doesn't stop people from mutating the value inside the DataTable it only stops people from trying to initialise the static member at the same time.

private static readonly object UnitTableLock = new object();
private static DataTable unitTable;
private static bool _ready = false;

public static DataTable GetUnitList()
{
    if (!_ready)
    {
        lock (UnitTableLock)
        {
            if (!_ready)
            {
                unitTable = new DataTable; //... etc
                System.Threading.Thread.MemoryBarrier();
                _ready = true;
            }
        }
    }

    return unitTable;
}

Only read from the result of GetUnitList never write to it.

Amended with reference to http://en.wikipedia.org/wiki/Double-checked_locking

I thought it would be worth adding that Double Check Locking has since been implemented in .net framework 4.0 in a class named Lazy . So if you would like your class to include the locking by default then you can use it like this:

public class MySingleton
{
    private static readonly Lazy<MySingleton> _mySingleton = new Lazy<MySingleton>(() => new MySingleton());

    private MySingleton() { }

    public static MySingleton Instance
    {
        get
        {
            return _mySingleton.Value;
        }
    }
}

They are not thread safe. You should think about making your logic thread safe by your self, for example, by using lock operator.

如果您使用.net 4,则可以在数据表上使用ThreadLocal包装器

Static variables aren't thread safe per-se. You should design with thread safety in mind.

There's a good link to get you started: http://en.csharp-online.net/Singleton_design_pattern%3A_Thread-safe_Singleton

Apart from this, I would strongly recommend you to use a more modern approach than the legacy DataTable. Check out the Entity Framework or NHibernate. Implementing them in your datalayer will allow you to hide database details from the rest of the software and let it work on a higher level abstraction (POCO objects).

I think you should be fine. There is a liight chance that 2 threads will determine that the datatable is null and both read the table, but only one gets to assign the unitTable / currencyTable reference last, so worst case you be initalizing them more than once. But once they're set I think you'd be good. AS LONG AS YOU DON'T WRITE TO THEM. Theat could leave one in an inconsistent state.

If you want to avoid the double init you could wrap the whole getter code in a lock statement. It's a lot like initializing a singleton.

Also add a method that let's you set the references to null again so you can force a refresh.

GJ

If the DataTables are read-only then you should lock them when you populate them and if they never change then they will be thread safe.

public class BusinessLayerHandler
{
    public static DataTable unitTable;
    public static DataTable currencyTable;

    private static readonly object unitTableLock = new object();
    private static readonly object currencyTableLock = new object();

    public static DataTable GetUnitList()
    {
        //import lists each time the application is run
        lock(unitTableLock)
        {
            if (unitTable == null)
            {
                unitTable = DatabaseHandler.GetUnitList();
            }
        }

        return unitTable;
    }

    public static DataTable GetCurrencyList()
    {
        //import lists each time the application is run
        lock(currencyTableLock)
        {
            if (currencyTable == null)
            {
                currencyTable = DatabaseHandler.GetCurrencyList();
            }
        }

        return currencyTable;
    }
}

If you need really high performance on this lookup you can use the ReaderWriterLockSlim class instead of a full lock everytime to limit the number of waits that will happen in the application.

Check out http://kenegozi.com/blog/2010/08/15/readerwriterlockslim-vs-lock for a short article on the differences between lock and ReaderWriterLockSlim

EDIT: (Answer to comments below)

The unitTableLock object is used like a handle for the Monitor class in to synchronize against.

For a full overview of Theading and synchronization in the .NET framework I would point you over to this very extensive tutorial http://www.albahari.com/threading/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM