Java设计模式:单例
15 Jul 2014注:本文为译文,原文出处java-design-patterns-in-stories
单例是最广泛应用的Java设计模式之一. 它通过防止外部对象实例化和修改来控制对象的数量. 这个概念适用于那些当只有一个对象却可以更高效运作的系统,或者那些限制一个特定对象实例数量的系统,例如:
- 私有构造器 - 没有其他类可以实例化新对象
- 私有引用- 不存在外部修改
- 公有静态方法 -唯一可以获得对象的途径
单例的故事
这是一个简单的用例. 一个国家只能有一个总统. 因此当需要总统的时候, 只应该返回唯一的总统, 而不是创建一个新的. getPresident()方法将会保证只有一个总统会被创建.
类图和代码
预加载模式:
public class AmericaPresident {
private static final AmericaPresident thePresident = new AmericaPresident();
private AmericaPresident() {}
public static AmericaPresident getPresident() {
return thePresident;
}
}
thePresident声明为final, 因此它将总是持有同一个对象引用.
懒加载模式1(无法保证线程安全):
public class AmericaPresident {
private static AmericaPresident thePresident;
private AmericaPresident() {}
public static AmericaPresident getPresident() {
if (thePresident == null) {
thePresident = new AmericaPresident();
}
return thePresident;
}
}
懒加载模式2(线程安全):
public class AmericaPresident {
private static AmericaPresident thePresident;
private AmericaPresident (){}
public static synchronized AmericaPresident getPresident() {
if (thePresident == null) {
thePresident = new AmericaPresident();
}
return thePresident;
}
}
虽然上述方式保证了线程的安全, 但是效率很低,因此有人提出了一种新的方式:双重校验锁.
懒加载模式3(双重校验锁1):
public class AmericaPresident {
private volatile static AmericaPresident thePresident;
private AmericaPresident (){}
public static AmericaPresident getPresident() {
if (thePresident == null) {
synchronized (AmericaPresident.class) {
if (thePresident == null) {
thePresident = new AmericaPresident();
}
}
}
return thePresident;
}
}
开发人员试图建立一种保证单例,但是又不会在线程同步上损耗太多事件成本的方法.但是此方法也存在问题: 假设AmericaPresident还没有被实例化. 接着, 线程A通过helper==null的判断, 进入同步块并开始执行thePresident = new AmericaPresident(). 与此同时,在AmericaPresident构造函数还没有完成执行完成时候, 线程B判断thePresident不为null, 并且使用thePresident的实例变量, 则会接收到不正确的值. 参见CWE-609: Double-Checked Locking
懒加载模式4(双重校验锁2):
public class AmericaPresident {
private static AmericaPresident thePresident = null;
private AmericaPresident() { }
public static AmericaPresident getPresident() {
if(thePresident == null) {
synchronized(AmericaPresident.class) {
AmericaPresident temp = thePresident;
if(temp == null) {
temp = new AmericaPresident();
thePresident = temp
}
}
}
return thePresident;
}
}
public class AmericaPresident {
private static volatile AmericaPresident thePresident = null;
private AmericaPresident() { }
public static AmericaPresident getPresident() {
if(thePresident == null) {
synchronized(AmericaPresident.class) {
if(thePresident == null) {
thePresident = new AmericaPresident();
}
}
}
return thePresident;
}
}
Java Stand Library中单例模式的应用
java.lang.Runtime#getRuntime() 是一个JSL中被广泛使用的方法. getRunTime()返回关联当前Java应用程序的运行时对象.
class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
//...
}
这是一个使用getRunTime()的简单的例子. 它在Windows上读取一个网页.
Process p = Runtime.getRuntime().exec(
"C:/windows/system32/ping.exe programcreek.com");
//get process input stream and put it to buffered reader
BufferedReader input = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
System.out.println(line);
}
input.close();
输出:
Pinging programcreek.com [198.71.49.96] with 32 bytes of data:
Reply from 198.71.49.96: bytes=32 time=53ms TTL=47
Reply from 198.71.49.96: bytes=32 time=53ms TTL=47
Reply from 198.71.49.96: bytes=32 time=52ms TTL=47
Reply from 198.71.49.96: bytes=32 time=53ms TTL=47
Ping statistics for 198.71.49.96:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 52ms, Maximum = 53ms, Average = 52ms
另一个单例实现
私有构造器并不会防止通过反射进行实例化, Joshua Bloch (Effective Java)提出了一个更加优雅的单例实现. 如果你对于Enum不够熟悉, 下面则是一个Oracle中很好的例子.
public enum AmericaPresident{
INSTANCE;
public static void doSomething(){
//do something
}
}