都知道singleton模式的線程安全方式有兩種,一種是在GetInstance方法定義上加上synchronized的鎖. 另一種方式是申明成static類型的變量.
我一直有一個疑問.net如何保證static類型是線程安全(實例化變量的操作時間很長,如何保證線程安全)? 我做了如下實驗,的確是線程安全的.
[Form1.cs文件(對話框上僅一個按鈕)]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace WindowsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(new ThreadStart(ShowBaseSalary));
Thread thread2 = new Thread(new ThreadStart(ShowBonusAndSalary));
thread1.Start();
thread2.Start();
}
private void ShowBaseSalary()
{
MessageBox.Show(Test.GetBaseSalary());
}
private void ShowBonusAndSalary()
{
MessageBox.Show(Test.GetBonusAndSalary(500));
}
}
class Test
{
private static Employee employee = new Employee(29); //實例化變量的時間可能很長,如何保證線程安全(線程1實例過程中,線程1有可能執行到該代碼嗎?)??
private int m = 0;
public static string GetBaseSalary()
{
return employee.GetBaseSalary();
}
public static string GetBonusAndSalary(int nBonus)
{
return employee.GetBonusAndSalary(nBonus);
}
}
[Employee.cs]
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication2
{
class Employee
{
private const int SALARYLEVEL = 15; //工資基數
private int _baseSalary;
public Employee(int ages)
{
Random rnd = new Random();
int nValue = rnd.Next(100);
while (nValue !=26) //模擬類實例化執行很長時間
{
_baseSalary = ages * SALARYLEVEL * nValue / 100;
nValue = rnd.Next(100);
System.Threading.Thread.Sleep(1000);
}
}
//獲取加上獎金的工資
public string GetBonusAndSalary(int nBonus)
{
return Convert.ToString(_baseSalary + nBonus);
}
//獲取基本工資
public string GetBaseSalary()
{
return _baseSalary.ToString();
}
}
}
另,C#中不需要用鎖的線程安全的Singleton設計模式!
典型的線程安全的Singleton實現是使用double-checked locking的實現,但是在.NET中我們並不需要使用double-checked locking就可以優雅地實現Singleton模式。
這個優美的Singleton實現基礎是.NET對運行期初始化的完整定義。它的優美之處是不需要典型的double-checked locking。
當CLR加載class Singleton的時候,因爲Singleton沒有static variables需要被初始化,所以Singleton的初始化其實什麼也沒做。而對static class LazyHolder來說,直到它被執行的時候纔會被初始化。而static class LazyHolder只有載Singleton.GetInstance()被執行的時候纔會執行到。當第一次調用GetInstance()的時候 CLR纔會加載和初始化LazyHolder這個class。對於LazyHolder class的初始化就是對static variable Instance的初始化。而Instance的初始化就是執行Singleton的private constructor。因爲.NET保證class的initialization是serial的,就是說在加載和初始化的過程中我們不需要自己做同步。因爲初始化過程是一個serial的操作,所以對於後面的GetInstance,我們不需要做任何同步,它也會返回一個正確初始化的 Singleton對象。
實現代碼:
2 {
3 private static class LazyHolder
4 {
5 public static readonly Singleton Instance = new Singleton();
6 }
7
8 private Singleton()
9 {
10 }
11
12 public static Singleton GetInstance()
13 {
14 return LazyHolder.Instance;
15 }
16 }
public class Singleton
{
private static readonly Singleton Instance = new Singleton();
static Singleton()
{
}
public static Singleton GetInstance()
{
return Instance;
}
}
這個吧!
sealed class Singleton
{
private Singleton();
public static readonly Singleton Instance=new Singleton();
}
1,私有實例構造器保證了不會被實例化.
2,sealed保證了不會被繼承
3,readonly保證了實例化後的自動不會被修改
4,static 自動保證了線程安全的實例化.
5,沒有靜態構造函數保證了編譯器產生的構造函數是BeforeFieldInit,達到了lazy-load目的.
不過我覺的這種情況下爲什麼不要static class了?非要用Singleton模式?
public class Singleton
{
private static Singleton Instance = null;
private Singleton()
{
}
static Singleton()
{
Instance = new Singleton();
}
public static Singleton GetInstance()
{
return Singleton.Instance;
}
}