单例模式( Singleton )是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。实现单例模式的思路是:一个类能返回对象一个引用 ( 永远是同一个 ) 和一个获得该实例的方法(必须是静态方法,通常使用 getInstance 这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。 [ wikipedia]
FClipboard: TClipboard;
function Clipboard: TClipboard;
begin
if FClipboard = nil then
FClipboard := TClipboard.Create;
Result := FClipboard;
end ;
问题的提出:
上述代码不是线程安全的。假设 A 率先调用函数 Clipboard,局部变量 FClipboard会先进行实例化。在实例尚未完全创建完之前,如果 B 也尝试调用函数Clipboard,那么它也会去对 FClipboard 进行实例化。因为变量FClipboard此时依然为空指针。于是乎A、B分别创建了一个 TClipboard 的实例,单例不再单!其中的一个并且变成了一个内存泄漏。这种泄漏往往发送在构建函数需要耗费较长时间的情况下。如何将上述代码改成线程安全的呢?其实可以通过加入临界区处理来解决
var
FClipboard: TClipboard;
GClipboardLocker: TRTLCriticalSection;
function Clipboard: TClipboard;
begin
if FClipboard = nil then
begin
EnterCriticalSection(GClipboardLocker);
try
if FClipboard = nil then
FClipboard := TClipboard.Create;
finally
LeaveCriticalSection(GClipboardLocker);
end;
end;
Result := FClipboard ;
end;
initialization
InitializeCriticalSection(GClipboardLocker);
finalization
DeleteCriticalSection(GClipboardLocker);
end.
小结:
单例模式简约而不简单,当开发往多线程延伸之后,可能还会爆出更多的问题。单例就是一个全局变量,建议不要滥用,否则软件的层次结构会被破坏。