单例模式再议


1.前言

曾多次对unity或者c#在使用单例时的一些问题进行过讨论,即有充满戾气的,也有理性的总结与封装(封装成一个抽象类,使用时直接继承),但多次使用时还是有一些不同的想法,故此文章诞生。此文将从纯C# 层展开。

2.单例的几种方式

此部分为纯C#层面

2.1 非线程安全模式

此种线程不安全,即多个线程同时初次调用单例时会产生空引用问题。

public sealed class Singleton
{
    private static Singleton instance = null;

    private Singleton(){}

    public static Singleton instance
    {
        get
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}

2.2 简单线程安全模式

此方式通过锁实现线程安全,简单有效,但是由于获取单例时每次都需要走锁,会有一定的性能问题(普通应用下可以忽略不记)。

public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    Singleton(){}

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}
关于锁的问题可以参考:https://www.cnblogs.com/wolf-sun/p/4209521.html

2.3 双重验证线程安全模式

此种方式实在上一种方式中在过锁前加了一个判断,来避免多次过锁。

public sealed calss Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    } 
}

2.4 非锁模式的线程安全

即利用C# 静态方法或变量的特性,实现线程安全,即保证所有的线程在调用时,单例已经生成,不需要判断单例为空。此方案在unity中曾多次使用,如此文描述,但也会存在一些问题。

public sealed class Singleton
{
    //在Singleton第一次被调用时会执行instance的初始化
    private static readonly Singleton instance = new Singleton();

    //Explicit static consturctor to tell C# compiler 
    //not to mark type as beforefieldinit
    static Singleton()
    {
    }

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

2.5 其他模式

还有一些其他模式如完全延时加载以及Lazy等,再次不再赘述。

3.Unity单例模式

3.1 特殊性

在unity中使用单例时,有如下三个特殊性:
1)线程安全性。由于涉及渲染对象操作都是在主线程中,所以除数据类问题、或者纯C#层问题外,基本都是单线程,所以在很大程度上不用考虑单例的线程安全性。
2)脚本的挂载操作。由于unity中使用类时并非直接使用或者new出一个对象,所以即使使用单例也会存在不同游戏物体上会挂载多个单例类(手误操作或者多人协作等导致)。
3)初始化问题。数据初始化一般都在MonoBehaviour的Awake或者Start中进行,这导致一些问题不同于C#原生单例。

3.2 一个单例

回归到本质,在unity中要防止为了使用单例而使用,要做好前期规划,由于种种问题,很难做到两全,所以做好预防处理即可。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class XXXManager : MonoBehaviour
{
    private static XXXManager instance;

    public static XXXManager GetInstance()
    {
        if (instance == null)
        {
            Debug.LogError("Null reference of XXXManager instance");
        }
        return instance;
    }

    private void Awake()
    {
        if (instance != null)
        {
            Debug.Log("Multiple instance of XXXManager");
        }
        else
        {
            instance = this;
        }
    }
}

4.结论

无结论