《CLR via C#》读书笔记-线程同步(六)-C#中的单例模式

       单例模式是经常用到的,最常用的情况就是创建一个数据库连接。下面说一下C#的单例模式的实现方式。

2023-7-17更新

public class SQLiteDataAccess
{
        private static SQLiteDataAccess instance;
        private static readonly object locker = new object();
        private static SQLiteConnection sqlliteConnection;
        private SQLiteDataAccess()
        {
            //内部实现单例的数据库连接
            string dbPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "nmdatabase.db3");
            sqlliteConnection = new SQLiteConnection(dbPath);
        }

        public static SQLiteDataAccess GetInstance()
        {
            if (instance == null)
            {
                lock (locker)
                {
                    if (instance == null)
                    {
                        instance = new SQLiteDataAccess();
                    }
                }
            }
            return instance;
        }


        //返回数据库连接
        public SQLiteConnection GetSQLiteConnection()
        {
            if (instance == null) return null;

            return sqlliteConnection;
        }

        //创建数据库表
        public CreateTableResult CreateTable<T>() where T : class
        {

            CreateTableResult ctr = sqlliteConnection.CreateTable<T>(CreateFlags.None);

            return ctr;
        }


        public int AddModelData<T>(T model)
        {
            return sqlliteConnection.Insert(model);
        }

        //删除数据库中的全部数据
        //以便进行数据的重新加载
        //特别是字典数据
        public int DeleteAllData<T>()
        {
            return sqlliteConnection.DeleteAll<T>();
        }
        



        //关闭数据库连接
        public void CloseSQLite()
        {
            sqlliteConnection.Close();
        }

}

以下为原文:

实现方式1

        最严谨的方式:

public sealed class Singleton{
	//声明一个私有锁
	private static object m_lock = new object();
	
	//单例变量的声明
	private static Singleton s_value=null;
	
	//初始化的相关工作
	//这儿需要注意,构造器的是私有的
	private Singleton(){
		
	}
	
	//返回一个单例模式
	public static Singleton GetSingleton(){
		//
		if(s_value!=null) return s_value;
		
		//获得私有锁
		Monitor.Enter(m_lock);
		if(s_value==null){
			//这儿的写法是先声明一个temp,然后通过InterLocked进行赋值
			//目的就是保证,先完成“完整的”初始化后,再将对象引用赋给s_value
			//防止出现如下的情况:CLR先分配完Singleton的内存,然后将引用赋值给s_value,最后再调用构造器
			Singleton temp=new Singleton();
			InterLocked.Exchange(s_value,temp);
		}
		Monitor.Exit(m_lock);
		
		return s_value;
	}
	
}

        上面的代码就是C#中最严谨的单例模式的代码。C#可以“正确”的实现单例模式,但是Java不能保证在任何地方都能正常工作。其原因就是:JVM在GetSingleton方法中第一次读取时,将s_value读入寄存器中。进行到第二个if语句时,直接从寄存器中读取s_value的值。导致第二个if语句永远都是true。因此会存在多个线程同时创建Singleton对象,因此在Java中有如此表现。

        【2017-03-08补充】目前java也可以做到保证单例模式的正常了

public class Singleton
{
	//通过volatile关键字保证每次的数据读取都是从内存,而不是从寄存器
	//因此java也可以保证单例模式的“正常”
    private volatile static Singleton singleton = null;
    private Singleton()  {    }
    public static Singleton getInstance()   {
        if (singleton== null)  {
            synchronized (Singleton.class) {
                if (singleton== null)  {
					//和C#的一个区别就是,C#这儿使用了CompareExchange方法,保证了先生成实例,再进行赋值
                    singleton= new Singleton();
                }
            }
        }
        return singleton;
    }
}

实现方式2

        第一种方式较为繁琐,可直接利用CLR的一个特征:CLR保证类的构造器是线程安全的。因此可以使用粗暴的方式,如下:

public sealed class Singleton{
	
	//单例变量的声明
	private static Singleton s_value=new Singleton();
	
	//初始化的相关工作
	//这儿需要注意,构造器的是私有的
	private Singleton(){
		
	}
	
	//返回一个单例模式
	public static Singleton GetSingleton(){ return s_value;}
}

        这种方式简单粗暴。通过CLR的特征保证了线程安全,但是没有保证单例。因此这种方式不能称之为一个“单例模式”。因此对此方式进行修改

实现方式3

        根据方式2的修改,得到如下:

public sealed class Singleton{
	
	//单例变量的声明
	private static Singleton s_value=new Singleton();
	
	//初始化的相关工作
	//这儿需要注意,构造器的是私有的
	private Singleton(){
		
	}
	
	//返回一个单例模式
	public static Singleton GetSingleton(){

		if(s_value!=null) return s_value;
		
		//这儿会有竞争,有可能多个线程都进行到这步,创建了多个Singleton对象
		//但是通过调用CompareExchange方法,只有一个Singleton对象,将其赋予s_value
		Singleton temp=new Singleton();
		InterLocked.CompareExchange(s_value,temp,null);
		
		return s_value;
	}
}


        这儿也会存在一个小问题,就在Singleton temp=new Singleton();这句,多个线程可能会创建多个Singleton对象,但是通过CompareExchange方法,可以保证只有一个Singleton对象,其他的会被GC回收掉。优点就是快

4其他

        系统提供了一个System.Layz类,基本信息如下,其主要目的就是延迟初始化(延迟加载)

public class Lazy<T>{
	public Lazy(Func<T> valueFactory,LazyThreadSafeMode mode);
	public bool IsValueCreated{get;}
	public T Value{get;}
}

       使用的例子如下:

public static void Main(){
	//定义一个延迟加载变量
	Lazy<string> sLazy = new Lazy<string>(()=>DateTime.Now.ToString(),LazyThreadSafeMode.PublicationOnly);
	
	Console.WriteLine(sLazy.IsValueCreated);
	Console.WriteLine(sLazy.Value);
	Console.WriteLine(sLazy.IsValueCreated);
	Thread.Sleep(1000);
	Console.WriteLine(sLazy.Value);
}

        在构造器中还有一个参数:LazyThreadSafeMode,其定义如下:


 

        从上之下:ExecutionAndPublication使用了双锁技术;None线程不安全;PublicationOnly使用了CompareExchange技术。       

       另外还有一个延迟加载的静态方法System.Threading.LazyInitializer方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值