Static 關鍵字作為修飾符可以用於類、方法和成員變量上。其含義是對於整個應用程序生命周期內,訪問該修飾符修飾的對象/方法/變量都引用到同一實例(內存地址)。但正因如此在多線程下會出現線程安全問題:計數器字段count是靜態的,在多線程下循環調用1000次Increase方法,得到的結果未必是1000
internal class Counter { public static int count = 0; public static void Increase() { Thread.Sleep(10); Counter.count++; } }
要解決這個問題,首先要了解Static的特性:
1.若修飾一個變量,即使是個值類型,如Int變量,該變量無論在何處使用都是同一個實例(內存地址)。此時它表現出了引用類型的特性,在方法中調用該靜態變量,不是將該值類型放入棧中,而是指向該變量的指針。
2.靜態類、變量、方法的內存分配是在應用程序編譯時就已確定的,在應用程序初始化時就已分配好的,當應用程序生命周期結束時才被銷毀。且他們既不存儲在棧上也不在堆上,而是靜態數據區中。正因如此才能被整個應用程序全局的訪問到同一實例。 這裡有一篇文章描述了靜態存儲區的詳細:http://www.educity.cn/zk/bianyi/201307031614161496.htm
3.靜態類不能繼承或實例化,不能有普通構造函數,但可以有靜態構造,用來初始化其中的靜態成員。靜態類只能有靜態成員或方法。
4.靜態構造函數既可以用在靜態類中又可以用在普通類中。其作用就是為了提供合適的時機初始化靜態成員。對於一個普通類裡的靜態成員,在應用程序啟 動時即被初始化,此時連main函數都未開始執行,普通構造函數更是無法被調用。而擁有靜態構造方法的類,其靜態成員初始化將被延後到一旦類內任何靜態成 員或靜態方法被實際調用時,才會觸發默認的初始化,然後是觸發靜態構造方法。靜態構造只能被調用一次,只能初始化靜態成員。若直接初始化該類,則靜態構造先觸發,再觸發普通構造函數。
由此可見,最根本的原因是在同一時刻有兩個線程同時訪問靜態資源,資源的狀態同時修改而導致的。因此需要對共享資源局部串行化,也即線程安全的單例。
internal class LockCounter { public static int count = 0; public static void Increase() { Thread.Sleep(10); lock (typeof(LockCounter)) { LockCounter.count++; } } } View Code更進一步的,可以不適用Lock而是使用InterLocked進行原子操作
internal class InterLockCounter { public static int count = 0; public static void Increase() { Thread.Sleep(10); Interlocked.Increment(ref InterLockCounter.count); } } View Code而如果我們利用靜態構造的該特性,我們可以將它用在實現簡潔無鎖的單例模式,而且是線程安全的,因為任何試圖訪問實例的線程都會觸發靜態構造初始化該對象實例之後才能訪問,且靜態構造只能被調用一次,不會出現多線程問題。