设计模式之单例模式(2)

发布于:2022-12-27 ⋅ 阅读:(406) ⋅ 点赞:(0)

设计模式之单例模式(1)中给出了Java中的双检锁技术方案。

为什么需要volatile

设计模式之单例模式(1)中给出了Java中的双检锁技术方案。Javaz中为什么一定要用volatile,如果不用的话,会存在什么问题。

private static volatile Singleton instance

前面的双重检查示例代码创建一个对象。这一行代码可以分解为如下的3行伪代码。

memory=allocate();        //1:分配对象的内存空间
ctorInstance(memory);     //2:初始化对象
instance = memory;          //3:设置instance指向刚分配的内存地址

上面3行伪代码中的2和3之间,可能会被重排序。2和3重排序之后的执行时序如下。

memory=allocate();        //1:分配对象的内存空间
instance = memory;          //3:设置instance指向刚分配的内存地址
                            //注意,此时对象还没有被初始化!
ctorInstance(memory);     //2:初始化对象

在这里插入图片描述

由于单线程内要遵守intra-thread semantics,从而能保证A线程的执行结果不会被改变。但是,当线程A和B按上图时序执行时,B线程将看到一个还没有被初始化的对象。而使用volatile可以禁止这种重新排序

双检锁技术

这篇文章给出C#版本的双检锁技术代码:

namespace Singleton6
{
    internal sealed class LazySingleton
    {
        private static volatile LazySingleton singleton = null;
        private static Object m_obj = new();
        
       //私有构造器阻止这个类的任何外部代码创建实例
        private LazySingleton()
        {
        }
        public static LazySingleton GetLazySingleton()
        {
            if (singleton == null)
            {
                Monitor.Enter(m_obj);
                if (singleton == null)
                {
                    singleton = new LazySingleton();
                }
                Monitor.Exit(m_obj);
            }
            return singleton;
        }
    }
}

反编译的il代码如下:

// Type: Singleton6.LazySingleton 
// Assembly: Singleton6, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 1CB2B007-5286-449F-BF1D-C02A34D9C212
// Location: D:\vs2022\Project\Singleton6\bin\Debug\net6.0\Singleton6.dll
// Sequence point data from D:\vs2022\Project\Singleton6\bin\Debug\net6.0\Singleton6.pdb

.class private sealed auto ansi beforefieldinit
  Singleton6.LazySingleton
    extends [System.Runtime]System.Object
{
  .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor([in] unsigned int8)
    = (01 00 01 00 00 ) // .....
    // unsigned int8(1) // 0x01
  .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor([in] unsigned int8)
    = (01 00 00 00 00 ) // .....
    // unsigned int8(0) // 0x00

  .field private static class Singleton6.LazySingleton modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) singleton

  .field private static object m_obj

  .method private hidebysig specialname rtspecialname instance void
    .ctor() cil managed
  {
    .maxstack 8

    // [15 9 - 15 32]
    IL_0000: ldarg.0      // this
    IL_0001: call         instance void [System.Runtime]System.Object::.ctor()
    IL_0006: nop

    // [16 9 - 16 10]
    IL_0007: nop

    // [19 9 - 19 10]
    IL_0008: ret

  } // end of method LazySingleton::.ctor

  .method public hidebysig specialname instance object
    get_Obj() cil managed
  {
    .maxstack 8

    // [21 36 - 21 41]
    IL_0000: ldsfld       object Singleton6.LazySingleton::m_obj
    IL_0005: ret

  } // end of method LazySingleton::get_Obj

  .method public hidebysig specialname instance void
    set_Obj(
      object 'value'
    ) cil managed
  {
    .maxstack 8

    // [21 50 - 21 63]
    IL_0000: ldarg.1      // 'value'
    IL_0001: stsfld       object Singleton6.LazySingleton::m_obj
    IL_0006: ret

  } // end of method LazySingleton::set_Obj

  .method public hidebysig static class Singleton6.LazySingleton
    GetLazySingleton() cil managed
  {
    .maxstack 2
    .locals init (
      [0] bool V_0,
      [1] bool V_1,
      [2] class Singleton6.LazySingleton V_2
    )

    // [24 9 - 24 10]
    IL_0000: nop

    // [25 13 - 25 35]
    IL_0001: volatile.
    IL_0003: ldsfld       class Singleton6.LazySingleton modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) Singleton6.LazySingleton::singleton
    IL_0008: ldnull
    IL_0009: ceq
    IL_000b: stloc.0      // V_0

    IL_000c: ldloc.0      // V_0
    IL_000d: brfalse.s    IL_0043

    // [26 13 - 26 14]
    IL_000f: nop

    // [27 17 - 27 38]
    IL_0010: ldsfld       object Singleton6.LazySingleton::m_obj
    IL_0015: call         void [System.Threading]System.Threading.Monitor::Enter(object)
    IL_001a: nop

    // [28 17 - 28 39]
    IL_001b: volatile.
    IL_001d: ldsfld       class Singleton6.LazySingleton modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) Singleton6.LazySingleton::singleton
    IL_0022: ldnull
    IL_0023: ceq
    IL_0025: stloc.1      // V_1

    IL_0026: ldloc.1      // V_1
    IL_0027: brfalse.s    IL_0037

    // [29 17 - 29 18]
    IL_0029: nop

    // [30 21 - 30 53]
    IL_002a: newobj       instance void Singleton6.LazySingleton::.ctor()
    IL_002f: volatile.
    IL_0031: stsfld       class Singleton6.LazySingleton modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) Singleton6.LazySingleton::singleton

    // [31 17 - 31 18]
    IL_0036: nop

    // [32 17 - 32 37]
    IL_0037: ldsfld       object Singleton6.LazySingleton::m_obj
    IL_003c: call         void [System.Threading]System.Threading.Monitor::Exit(object)
    IL_0041: nop

    // [33 13 - 33 14]
    IL_0042: nop

    // [34 13 - 34 30]
    IL_0043: volatile.
    IL_0045: ldsfld       class Singleton6.LazySingleton modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) Singleton6.LazySingleton::singleton
    IL_004a: stloc.2      // V_2
    IL_004b: br.s         IL_004d

    // [35 9 - 35 10]
    IL_004d: ldloc.2      // V_2
    IL_004e: ret

  } // end of method LazySingleton::GetLazySingleton

  .method private hidebysig static specialname rtspecialname void
    .cctor() cil managed
  {
    .maxstack 8

    // [12 9 - 12 45]
    IL_0000: newobj       instance void [System.Runtime]System.Object::.ctor()
    IL_0005: stsfld       object Singleton6.LazySingleton::m_obj
    IL_000a: ret

  } // end of method LazySingleton::.cctor

  .property instance object Obj()
  {
    .get instance object Singleton6.LazySingleton::get_Obj()
    .set instance void Singleton6.LazySingleton::set_Obj(object)
  } // end of property LazySingleton::Obj
} // end of class Singleton6.LazySingleton

饿汉式

开发人员把双检索技术捧的太高了,在不该使用它的地方仍然在使用它。大多数时候,这个技术实际上会损害效率。下面这中的要简单的多。

    public sealed class Singleton {

        private static Singleton s_value = new Singleton();

        private Singleton()
        {

        }
        public static Singleton GetSingleton() { return s_value; }
    }

由于代码首次访问类的成员的时,CLR会自动调用类型的类构造器,所以首次有一个线程查询Singleton的GetSingleton的方法的时候,CLR会自动调用类构造器。从而创建一个对象实例。此外,CLR已保证了类构造器的调用是线程安全的。

非阻塞版本

来自CLR via C#,代码如下:

    public sealed class Singleton {

        private static Singleton s_value = null;

        private Singleton()
        {

        }

        public Singleton GetSingleton() {
            if (s_value != null) return s_value;

            //创建一个新的单实例对象,先把它固定下来(如果另一个线程还没有固定它的话)
            Singleton temp = new Singleton();
            Interlocked.CompareExchange(ref s_value, temp, null);

            //如果这个线程竞争失败,新建的第二个实例对象会被垃圾回收

            //返回对单实例对象的引用
            return s_value;
        }
    }

优势:速度快,永不阻塞线程。

//Int32 old = location1;
//if (location1 == comparand) location1 = value;
//return old;
public static Int32 CompareExchange(ref Int32 location1, Int32 value, Int comparand)

FCL

System.Lazy类封装了单例模式

public class Lazy<T> {
	public Lazy(FunC<T> valueFactory, LazyThreadSafeMode mode);
	public Boolean IsValueCreated { get; }
	public T value { get; }
}
public static void Main(string[] args)
{
	  Console.WriteLine("Hello World!");
	  Lazy<String> s = new Lazy<string>(() => DateTime.Now.ToLongTimeString(), true);
	  Console.WriteLine(s.IsValueCreated);//好没有查询value 所以返回false
	  Console.WriteLine(s.Value);//现在调用委托
	  Console.WriteLine(s.IsValueCreated);// 已经查询了value 返回true
	  Thread.Sleep(1000);
	  Console.WriteLine(s.Value);//委托没有调用,显示相同的结果

}

内存有限时,可以使用System.Threading.LazyInitializer类的静态方法。

    class MainClass
    {
        public static void Main(string[] args)
        {
            string name = null;
            //由于name为null,所以委托执行
            LazyInitializer.EnsureInitialized(ref name, () => "Burning");
            Console.WriteLine(name);
            //由于name不为null,所以委托不运行,name不变
            LazyInitializer.EnsureInitialized(ref name, () => "无情剑客");
            Console.WriteLine(name);

        }
    }

最终运行结果:
在这里插入图片描述

参考

https://www.jianshu.com/p/45885e50d1c4
CLR via C#(第四版)
https://www.cnblogs.com/xz816111/p/8470048.html

写在最后

这个算是补上8月分的,8月事情比较多。好久没写破解相关的文章了,接下来会写一些破解的文章,破解的目标是虎牙直播客户端。欢迎大家关注。

公众号

更多内容,欢迎关注我的微信公众号: 半夏之夜的无情剑客。
在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到