C#: 双检锁 (Double Checked Locking)

var jiathis_config = {data_track_clickback:'true'};



转自:http://nap7.com/me/double-checked-locking/
以下内容是我在公司作为新人培训讲师时对于作业的一次评价,简单介绍了双解锁的作用,可以作为一个简单的参考。

大家可以思考这样一个问题,一个程序可以对应多少个日志文件?对于我们这个小程序来说1个就够了,很多同学在设计Logger类的时候都是在构造方法或初始化方法中生成日志文件的,也就是说,这基本上等价于一个Logger的实例对应一个新的日志文件(或重新对同一文件重新开启流)。

1
Logger myLogger = new Logger(@“D:my.log”);

如何才能阻止Logger被随意的new出实例呢?我们可以修改Logger的构造方法,让构造方法成为private的,这样就能实现谁都不能new出Logger实例的目的了。但是,访问修饰符(如private)只是影响类之外的使用,对于Logger类的内部,是不会受到private的影响的,也就是说,我们依然可以在Logger类中使用new来创建实例,这正是我们想要的,我们可以为用户提前创建好一个实例,并作为这个类的静态成员存在,从而得到这样一个Logger类:

1
2
3
4
5
6
7
8
9
public class Logger
{
     private static Logger instance = new Logger();
     private Logger() { }
     public static Logger GetInstance()
     {
         return instance;
     }
}

通过以上的代码,我们就可以使用GetInstance() 方法来获取被提前创建出来的Logger类的实例,而且每次调用GetInstance() 所获得到的对象都是同一个实例。这种方式就叫做单例模式。

单例模式在实现上分为两种,饿汉模式和懒汉模式,上边的实现就是饿汉模式,它在类初始化的过程中就已经把单例instance对象创建好了,而另一种方式则是现用现加载(延迟加载)也就是懒汉模式,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Logger
{
     private static Logger instance;
     private Logger() { }
     public static Logger GetInstance()
     {
         if (instance == null )
         {
             instance = new Logger();
         }
         return instance;
     }
}

但是这种实现存在线程安全问题的,例如现在有A、B两个线程,当A线程调用了GetInstance() 方法,并在黄色位置处进行了实例的空判别,并且进入了if逻辑,而这是发生了线程切换(线程切换是不可预知的,随机发生的),B线程也调用了GetInstance() 方法,其在黄色位置处也进行了判空操作,而A线程并没有完成new操作,所以B线程依然进入了if体内,准备new出实例。随后,假设线程切换回A,A创建出实例并返回了实例A,而后B线程继续,B有new除了一个实例而返回了另一个实例。那么,对于A和B线程,他们所得到的实例就是不同的实例了。为了避免这种情况的发生,我们需要为其添加一个锁,来实现线程安全。

1
2
3
4
5
6
7
8
9
10
11
public static Logger GetInstance()
{
     lock (initLockHelper)
     {
         if (instance == null )
         {
             instance = new Logger();
         }
     }
     return instance;
}

但是,这个锁的目的是为了防止首次创建对象时发生的线程问题而增加的,对于之后的更多时间里,我们是不需要再进行加锁操作的,这个操作的资源消耗还是比较大的,因此,我们需要在lock之前先一次检查一下instance是否为null:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static Logger GetInstance()
{
     if (instance == null )
     {
         lock (initLockHelper)
         {
             if (instance == null )
             {
                 instance = new Logger();
             }
         }
     }
     return instance;
}

这种锁机制我们称为 双检锁 (Double Checked Locking)机制,这样既保证了效率,又保证了线程安全。当我们的对象是一个轻量级类型时(类中没有太多的资源,比较简单)这是应该优先考虑使用饿汉模式,而对于类型复杂、资源占用较多的对象,可以考虑现用现加载,即懒汉模式。

除了上述介绍的单例模式,其实还有多例模式,我们可以在Logger类中维护一个Dictionary对象,其中的成员就是具体的一个个实例,我们可以指定一个名字来获得对应的对象,比如名为 ModuleALogger、和ModuleBLogger分别对应两个不同的实例,随后可以通过Logger GetInstance(string instanceName) 来获得具体的实例。

关于Logger的实现,以下是一个简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
namespace Common.LogHelper
{
     #region using directives
 
     using System;
     using System.IO;
     using System.Text;
 
     #endregion using directives
 
     /// <summary>
     ///     日志记录类,内容将会以UTF-16编码保存
     /// </summary>
 
 
     public class FileLogHelper : ILogHelper
     {
         private static FileLogHelper logHelper;
 
         private static readonly object initLockHelper = new object ();
         private static readonly object writeLockHelper = new object ();
         private static readonly object disposeLockHelper = new object ();
         private FileStream fileStream;
 
         /// <summary>
         ///     定义是否将日志消息输出至终端屏幕
         /// </summary>
         private Boolean isShowMsg;
 
         /// <summary>
         ///     日志文件的位置
         /// </summary>
         private String logFilePath;
 
         private StreamWriter streamWriter;
 
         private FileLogHelper()
         {
         }
 
         public String LoggerFullPath
         {
             get { return this .logFilePath; }
         }
 
         /// <summary>
         ///     初始化日志记录器,在指定位置创建日志文件
         /// </summary>
         /// <param name="logFileSavePath">日志文件指定的位置及名称</param>
         /// <param name="showMsgToScreen">是否同时将信息显示在终端窗口</param>
         /// <returns>是否成功生成</returns>
         public void InitLogHelper(String logFileSavePath, Boolean showMsgToScreen = false )
         {
             if (String.IsNullOrEmpty(logFileSavePath))
             {
                 throw new ArgumentNullException( "logFileSavePath" );
             }
             try
             {
                 // 判断指定目录是否存在,不存在则自动生成
                 var logDirPath = Path.GetDirectoryName(logFileSavePath);
                 if (logDirPath == null )
                 {
                     throw new ArgumentNullException( "logFileSavePath" );
                 }
                 if (!Directory.Exists(logDirPath))
                 {
                     Directory.CreateDirectory(logDirPath);
                 }
                 this .logFilePath = logFileSavePath;
                 this .isShowMsg = showMsgToScreen;
                 if (!File.Exists(logFileSavePath))
                 {
                     File.Create(logFileSavePath).Close();
                 }
                 this .fileStream = new FileStream( this .logFilePath, FileMode.Append);
                 this .streamWriter = new StreamWriter( this .fileStream, Encoding.Unicode);
                 this .WriteLog( @"Initial Log Writer Successful." );
             }
             catch (Exception ex)
             {
                 throw new Exception( @"Create Log File Fail." , ex);
             }
         }
 
         /// <summary>
         ///     向日志文件中追加日志消息
         /// </summary>
         /// <param name="logText">日志的消息内容</param>
         /// <param name="logType">消息的类型</param>
         /// <returns>日志是否添加成功</returns>
         /// <exception cref="System.ArgumentNullException" />
         /// <exception cref="System.Exception" />
         public void WriteLog(String logText, LogType logType = LogType.Info)
         {
             lock (writeLockHelper)
             {
                 if (String.IsNullOrEmpty( this .logFilePath))
                 {
                     throw new Exception( @"Please initial FileLogHelper at first." );
                 }
                 try
                 {
                     String infoText;
                     switch (logType)
                     {
                         case LogType.Error:
                             infoText = "X" + DateTime.Now + "tProgram Error.t" + logText;
                             break ;
 
                         case LogType.Warning:
                             infoText = "#" + DateTime.Now + "tProgram Warning.t" + logText;
                             break ;
 
                         case LogType.Info:
                             infoText = "@" + DateTime.Now + "tProgram Info.t" + logText;
                             break ;
 
                         case LogType.Debug:
                             infoText = "*" + DateTime.Now + "t*DEBUG INFO*t" + logText;
                             break ;
 
                         default :
                             infoText = "X" + DateTime.Now + "tLogHelper Exception, Invalid LogType.t" + logText;
                             break ;
                     }
                     if ( this .isShowMsg)
                     {
                         Console.WriteLine(infoText);
                     }
                     this .streamWriter.WriteLine(infoText);
                     this .streamWriter.Flush();
                 }
                 catch (Exception ex)
                 {
                     throw new Exception( "Can NOT Writting to Log File: " + this .logFilePath, ex);
                 }
             }
         }
 
         /// <summary>
         ///     释放日志记录器所占用的相关资源,无需手动调用
         /// </summary>
         public void Dispose()
         {
             if ( this .streamWriter != null )
             {
                 lock (disposeLockHelper)
                 {
                     if ( this .streamWriter != null )
                     {
                         if ( this .streamWriter.BaseStream.CanRead)
                         {
                             this .streamWriter.Dispose();
                         }
                         this .streamWriter = null ;
                     }
                     if ( this .fileStream != null )
                     {
                         if ( this .fileStream.CanRead)
                         {
                             this .fileStream.Dispose();
                         }
                         this .fileStream = null ;
                     }
                     logHelper = null ;
                 }
             }
         }
 
         /// <summary>
         ///     获取唯一实例(线程安全)
         /// </summary>
         /// <returns>日志记录器唯一实例</returns>
         public static ILogHelper GetInstance()
         {
             if (logHelper == null )
             {
                 lock (initLockHelper)
                 {
                     if (logHelper == null )
                     {
                         logHelper = new FileLogHelper();
                     }
                 }
             }
             return logHelper;
         }
 
         /// <summary>
         ///     析构函数,用于GC的自动调用
         /// </summary>
         ~FileLogHelper()
         {
             this .Dispose();
         }
     }
}

你可以从维基百科上了解更多的内容。维基百科

double_checking

        </div>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值