首页 > 代码库 > 【C#】编程点滴:关于Static的思考

【C#】编程点滴:关于Static的思考

在ADO.NET数据访问层我们通常会将数据库连接字符串写成这样private static readonly string _conStr = "server=.;database=Product;uid=sa;pwd=sa";、这样的情况很多,比如单例模式。可能是出于约定俗成吧,也可能是习惯了"海淘"人家的,很多东西也没深入去考虑为什么要这样写(说我自己)。或许是看人家都这么写。那么为什么要用static来修饰?为什么还要加上readonly?又为什么说静态的是线程安全的呢?为了解释这些问题。我想我还得必须了解static的"前世今生",也就是为什么要有static这个关键字了。

先来看msdn关于静态类和静态类成员的解释:

静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数。静态类成员可用于分离独立于任何对象标识的数据和行为:无论对象发生什么更改,这些数据和函数都不会随之变化。当类中没有依赖对象标识的数据或行为时,就可以使用静态类。

言简意赅。不过有点抽象。从这里似乎看不出,它和线程安全有什么直接的关系。那么我们来看一篇人家写的文章吧(为了阅读方便。我就直接贴出来了):

在.Net中,Static会经常和线程的东西扯在一起。写的代码是不是线程安全呢?好多程序员都在想,不过,有时候随便就放过了。真正出问题的时候再想。其实,如果程序员一开始就明白这里面的机制,也许,编写的代码质量会更加的高。也少些线程安全的烦问题。事实上,了解线程安全的机制后,你会发现,这原来是很简单的事情,只是很多人不愿意去了解线程(仔细一想,有的时候自己我(不是文章的作者)何尝不是这样)。因为对于普通的程序员,线程的东西真是不好玩的,也不太过瘾,同时好容易出错。生活中的例子顾客一次买了10个产品去收银处交钱,一般来说,收银处的框台应该很多的,顾客可以选一个人少的结帐就行了。而收银员可以一个个顾客地处理。这当然是有条不紊的事了。实际上,每个收银员就是一个线程,如果一个框台对于一个收银员的话,也就真是没有问题了。框台就是一个资源。

但是对于操作系统而言,资源往往是很宝贵的,要提供给多个收银员使用。即线程共享地使用资源。而操作系统的调度经常是没有道理的,也就是随意性很大。一时由收银员A操作,一时由收银员B操作。
试想想,收银员A,B都不能很好地做自己的事情,而且操作的数据会很混乱。也根本不知道对不对。不过,如果收银员A可以在一段时间内独占框台,事情也就会很容易了。这样虽然导致排队的用户很多,效率也低,但是每一个顾客的数据都处理正确的。
这其实就是线程安全的问题,如果一个线程把资源锁住了,别的线程在排除,数据就会很正确的

Static代码例子
下面的代码,无论你使用多少的线程来控件(制),都是安全的。因为线程之间,不会共享什么资源,唯一相同的是使用了同一个逻辑。其实这是在破坏线程的前题方面下功夫,出现线程不安全的条件都没有了,那肯定就是线程安全的

class CheckoutLane
{
    public static float GetTotal(Cart cart)
    {
        float total = 0;

        for (int i = 0; i < cart.GroceryItems.Length; i++)
        {
            total += cart.GroceryItems[i].Price; 
            Thread.Sleep(100);
        }
        return total;
    }
}
View Code

而下面的代码,不是线程安全的,因为共享了static float total;这个资源,而各个线程都被随机调用,可以任意修改total这个数据。这个,就正如多个收银员共享柜台,任意执行收银操作一样。

class CheckoutLane
{
    static float total;

    public static float GetTotal(Cart cart)
    {
        total = 0;

        for (int i = 0; i < cart.GroceryItems.Length; i++)
        {
            total += cart.GroceryItems[i].Price;  
            Thread.Sleep(100);
        }
        return total;
    }

}
View Code

下面的代码是线程安全的,使用了lock关键字,可以达到一个线程强占资源的效果。这时,synchLock就作为了共享的资源,被某一个线程锁住了。

class CheckoutLane
{
    static float total;
    static object synchLock = new object();

    public static float GetTotal(Cart cart)
    {
        lock(synchLock)
        {
            total = 0;

            for (int i = 0; i < cart.GroceryItems.Length; i++)
            {
                total += cart.GroceryItems[i].Price;  
                Thread.Sleep(100);
            }
            return total;
        }
    }
}
View Code

哈哈,人家的文章写得确实很不错。所以直接"舶来"品一下。这篇文章讲的很清楚。Static是线程安全的原因。这也给我提示为什么在类型的实例化的方面最好不要使用是static来修饰。因为使用它相当于将资源加上了锁。就像公共厕所,你占住了。别人就要等,特别是便池不够的时候。这当然会让别人感到很无奈。浪费了别人了时间。万一别人尿急了妮,是不是?这就是很多说的为什么使用static会对性能造成影响的原因了。但是也只有这样才能保证每一个人都很守秩序。要是大家随便乱闯,可就有走光的危险了。如果这个static修饰的变量是全局的我们往往给他加上readonly来修饰。因为static修饰的变量还是可以被重新赋值,而它又是被共享的了。加上readonly就是只读了,保证了数据的安全。