转自: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();
}
}
}
|
你可以从维基百科上了解更多的内容。维基百科
</div>