在设计模式之单例模式(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月事情比较多。好久没写破解相关的文章了,接下来会写一些破解的文章,破解的目标是虎牙直播客户端。欢迎大家关注。
公众号
更多内容,欢迎关注我的微信公众号: 半夏之夜的无情剑客。