I. 서론
싱글 톤 패턴은 비교적 간단 23 개 디자인 패턴해야한다, 그것은 관심의 객체를 생성, 디자인 패턴의 유형을 만들 속한다.
둘째, 개념
싱글은 23 "명 중 4"디자인 패턴 중 하나 는 반복적 인 설계 문제를 해결하는 방법을 설명 가요 재사용 객체 지향 소프트웨어 개체 변경 테스트 및 재사용의 실현을 설계하기 위해서는 더 편리합니다.
싱글 톤 패턴은 다음과 같은 문제를 해결 :
-
어떻게 클래스의 인스턴스가 하나만을 보장하기 위해?
- 어떻게 쉽게 클래스의 고유 한 인스턴스에 액세스?
-
어떻게 클래스의 인스턴스를 통제하기 위해?
-
어떻게 클래스의 인스턴스의 수를 제한하려면?
싱글은 위의 문제를 해결하는 방법은?
- 클래스 뒤에 생성자.
- 클래스 반환 공공 정적 작업의 고유 한 인스턴스를 정의합니다.
이것의 핵심은 디자인 패턴 클래스는 자신의 인스턴스를 제어하는 것입니다.
클래스 생성자 (정의 개인 생성자) 뒤에 클래스가 외부에서 인스턴스화 할 수없는 것을 확인합니다.
정적 기능을 사용하여 쉽게 클래스의 인스턴스에 액세스 (Singleton.getInstance ()).
셋째, 달성하기 위해
도 1에서, 예를 지연 단일
. (1) 은 USING 하여 System.Threading 단계; (2) . (3) 공용 클래스 SingletonTest을 4. { 5. 개인 정적 SingletonTest 인스턴스 = 널을 ; . 6 (7). /// <요약> 8. /// 숨겨진 클래스 생성자 (프라이빗 생성자)를 외부로부터 클래스 수 없도록 인스턴스화 . 9 /// </ 요약> 10 개인 SingletonTest () . 11 { 12은 Console.WriteLine ( " ****** 단일 클래스가 인스턴스화 ****** " ); 13 } 14 15 // / <요약> 16 /// 정적 클래스 인스턴스 쉽게 액세스 기능을 사용 . 17 /// </ 요약> 18이다 /// <반품> <= CREF 참조 "SingletonTest"/> </ 반환> . 19 공개 정적 하는 GetInstance () SingletonTest 20은 { 21 인 경우 (인스턴스 == 널 ) (22)가 { 23 인 경우 = 새로운 새 ; SingletonTest () (24) } (25) (26)는 창 , 예 27 } 28 29 공중 공극 ) (PrintSomething 30 { 31 Console.WriteLine ($ "当前线程아이디为{Thread.CurrentThread.ManagedThreadId} " ); 32 Console.WriteLine ( " 싱글 테스트 패턴 " ); 33 } 34 }
통상의 단일 스레드 동작의 경우, 상기 코드는, 하나의 실시 형태의 정의를 충족시킨다.
그러나 상황을 멀티 스레드?
우리는 테스트하려면 다음 코드를 사용하여
1 // 多线程情况 2 대 ( int로 I = 0 ; I는 < 10 ; ++ i가 ) 3 { 4 Task.Run ( 5 () => 6 { 7 SingletonTest singleton1 = SingletonTest.GetInstance () 8 singleton1.PrintSomething () , 9 }); 10 }
결과는 다음과 같다
예상대로, SingletonTest 클래스는이 문제를 해결하는 방법을 다음 싱글의 요구 사항을 충족하지 않는, 여러 번 인스턴스화?
그것은 여러 스레드로 인한 문제가 있기 때문에, 스레드 동기화를 사용해야합니다. 우리는 잠금 장치의 사용을 구현하기 위해 여기에 있습니다.
. (1) 은 USING 시스템, (2) 은 USING 하여 System.Threading 단계; . 3 . 4 공용 클래스 SingletonTest . 5 { . 6 개인 정적 SingletonTest 인스턴스 = 널 ] . 7 . 8 개인 정적 오브젝트 lockObject = 새로운 새로운 객체 () . (9) (10) /// <요약> . 11 /// 감추기 클래스 (프라이빗 생성자)의 생성자는 클래스가 외부로부터 인스턴스화 할 수 있도록 12 /// </ 요약> 13 인 전용 ) (SingletonTest을 14 { 15 에 Thread.sleep ( 500 ); 16 Console.WriteLine ( " ****** 단일 클래스가 인스턴스화 ****** " ) . (17) } (18)이다 .도 19 /// <요약> 20 /// 용이 정적 클래스의 인스턴스를 이용하여 액세스 기능 (21) / // </ 요약> 22 인 /// <반품> <= CREF "SingletonTest"참조 /> </ 반환> (23)는 공용 정적 하는 GetInstance SingletonTest () 24 { 25 // 재확인 잠금 단일 모드 실시 달성하는 수단 (26) 인 경우 ( == null의 예) 27 { 28 잠금 (lockObject) 29 { 30 경우 (예 == NULL) 31 { 32 인스턴스 = 새로운 SingletonTest (); 33 } 34 } 35 } 36 37 복귀 인스턴스; 38 } 39 40 공중 공극 PrintSomething () 41 { 42 Console.WriteLine ($ " 当前线程이드为Thread.CurrentThread.ManagedThreadId {} " ); 43 Console.WriteLine ( " 싱글 테스트 패턴 " ); 44 } 45 }
코드는, 인스턴스를 생성하는 두 가지 예시가 비어 있는지 확인하기 전에, 그 필요가 있는지의 첫 번째 문장은 (상기 코드 라인 26)를 비우는? 이유는 무엇입니까? 우리는 클래스 인스턴스를 초기화 된 경우에 이러한 시나리오를 상상 내가 잠금을 획득해야합니까? 대답이 필요하므로하지 않은 제 1 판정 조건의 부가 (상기 코드 라인 26).
다음과 같은 결과가 다시 테스트 코드를 실행합니다 :
결과에서, 클래스는 멀티 스레드 낮은 클래스를 여러 번 문제를 인스턴스화를 해결하기 위해, 한 번만 인스턴스화됩니다. 그러나이 명령은 영향력 재정렬 여부? 휘발성 키워드 여부? 에 오신 것을 환영합니다 추장은 대답합니다.
2, 하나의 예를 굶주리는 (권장)
조금 복잡 첫 번째 방법은, 당신은 단순히 그것을 가리킬 수 있습니다? 다음과 같이
1 using System; 2 using System.Threading; 3 4 public class SingletonTest2 5 { 6 // 静态变量的方式实现单例模式 7 private static readonly SingletonTest2 Instance = new SingletonTest2(); 8 9 private SingletonTest2() 10 { 11 Thread.Sleep(1000); 12 Console.WriteLine("******单例类被实例化******"); 13 } 14 15 public static SingletonTest2 GetInstance() 16 { 17 return Instance; 18 } 19 20 public void PrintSomething() 21 { 22 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}"); 23 Console.WriteLine("Singleton Pattern Test"); 24 } 25 }
这种实现方式利用的是.NET中静态关键字static的特性,使单例类在使用前被实例化,并且只实例化一次,这个由.NET框架保证。
这种方式存在问题,在没有使用到类中的成员时候就创建实例了,能否在使用到类成员的时候才创建实例呢?如下图
1 using System; 2 using System.Threading; 3 4 public class SingletonTest2 5 { 6 // 静态变量的方式实现单例模式 7 private static readonly Lazy<SingletonTest2> Instance = new Lazy<SingletonTest2>(() => new SingletonTest2()); 8 9 private SingletonTest2() 10 { 11 Thread.Sleep(1000); 12 Console.WriteLine("******初始化单例模式实例*****"); 13 } 14 15 public static SingletonTest2 GetInstance() 16 { 17 return Instance.Value; 18 } 19 20 public void PrintSomething() 21 { 22 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}"); 23 Console.WriteLine("Singleton Pattern Test"); 24 } 25 }
我们使用了Lazy关键字来延迟实例化。
四、例外
值得注意的是,反射会破坏单例模式,如下代码,能直接调用类的私有构造函数,再次实例化。
1 // 反射破坏单例 2 var singletonInstance = System.Activator.CreateInstance(typeof(SingletonTest2), true);
怎么避免呢?类的实例化都需要调用构造函数,那么我们在构造函数中加入判断标识即可。尝试实例化第二次的时候,就会抛异常。
1 private static bool isInstantiated; 2 3 private SingletonTest2() 4 { 5 if (isInstantiated) 6 { 7 throw new Exception("已经被实例化了,不能再次实例化"); 8 } 9 10 isInstantiated = true; 11 Thread.Sleep(1000); 12 Console.WriteLine("******单例类被实例化******"); 13 }
五、应用
那么实际应用中,哪些地方应该用单例模式呢?在这个类只应该存在一个对象的情况下使用。哪些地方用到了单例模式呢?
- Windows任务管理器
- HttpContext.Current
六、总结
俗话说,凡事都有两面性。单例模式确保了类只有一个实例,也引入了其他问题:
- 单例模式中的唯一实例变量是使用static标记的,会常驻内存,不被GC回收,长期占用了内存
- 在多线程的情况下,使用的都是同一个实例,所以需要保证类中的成员都是线程安全,不然可能会导致数据混乱的情况
代码下载:https://github.com/hzhhhbb/SingletonPattern
七、参考资料
- https://en.wikipedia.org/wiki/Singleton_pattern
- https://docs.microsoft.com/zh-cn/dotnet/api/system.lazy-1?view=netcore-3.0