Singleton模式和它的变体Double-Checked Locking模式比较简单,而且非常常用.
按照<设计模式>一书 ,singleton 意图:保证一个类有且仅有一个实例,并对外提供一个访问它的全局访问点.
其具体的关键特征和工作原理,不在此文表述,可以参看<Design Patterns Explained>一书.
以下逐步提供代码实现:
代码1.
最经典的实现: 把class中所有的methods和fields/status全部声明为static,
然后初始化还可以用static关键字,显示的初始化.
(有兴趣的请参见<Thinking.In.Java.4th.Edition>一书中的节Explicit static initialization)
示例:
public class StaticSingleton {
public static int count =0;
private static String name =null;
static {
name = "singleton";
}
public static String getName() {
return name;
}
public static void setName(String name) {
StaticSingleton.name = name;
}
}
假设该StaticSingleton 有许多的methods和fields/status,那么我们的代码就有所多static关键字了.
这样对于声明的methods和fields/status都要打上static关键字.似乎有点累,
书写代码后,看来似乎有点不美观,
且对于JDK6开始,StaticSingleton 对外的引用全要写成StaticSingleton.A ,或StaticSingleton.getA()之类的,
让StaticSingleton 的局外使用不够简洁.
那么产生代码2.
代码2.Eager式的Singleton. (实际上多线程不可行)
示例:
public class EagerSingleton1 {
private static EagerSingleton1 instance;
private EagerSingleton1() {
}
public static EagerSingleton1 getInstance() {
if( instance == null ) instance = new EagerSingleton1();
return instance;
}
}
带父类的.
示例:
public class EagerSingleton1 extends SuperSingleton {
private static SuperSingleton instance;
private EagerSingleton1() {
}
public static SuperSingleton getInstance() {
if( instance == null ) instance = new EagerSingleton1();
return instance;
}
}
代码只能用于单线程情况,假设多线程情况下,那么就会出现问题.
如两个线程都执行EagerSingleton1 的new 操作,那后果就可以想象一下.
因此在多线程下我们加入线程同步代码像代码4 或则 像代码3.
代码3.
示例:
public class EagerSingleton {
//是否带final,视情况而定.
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
带父类的代码(略).
代码4. (针对JAVA,多线程还是不可行,原因JAVA编译器本身优化工作)
针对代码2解决方法:在检查null之后进行同步(doSync()),然后再检查一次,确保实例尚未创建.
这就称为Double-Checked Locking模式.
实际上这是C++转换成JAVA的.
可以带父类的:
示例:
public class LazySingleton extends SuperSingleton {
// 是否带final,视情况而定.
private static volatile LazySingleton instance = null;
private LazySingleton() {
}
public static SuperSingleton getInstance() {
if( instance == null ) {
synchronized (LazySingleton.class) {
if( instance == null ) {
instance = new LazySingleton();
}
}
}
return instance;
}
//private synchronized static void doSync() {
// if( instance == null ) instance = new LazySingleton();
//}
}
根据Java的语言规范,上面的代码是不可靠的。
出现上述问题, 最重要的2个原因如下:
1, 编译器优化了程序指令, 以加快cpu处理速度.
2, 多核cpu动态调整指令顺序, 以加快并行运算能力.
问题出现的顺序:
1, 线程A, 发现对象未实例化, 准备开始实例化
2, 由于编译器优化了程序指令, 允许对象在构造函数未调用完前, 将 共享变量的引用指向 部分构造的对象, 虽然对象未完全实例化, 但已经不为null了.
3, 线程B, 发现部分构造的对象已不是null, 则直接返回了该对象.
不过, 一些著名的开源框架, 包括jive,lenya等也都在使用DCL模式, 且未见一些极端异常.
说明, DCL失效问题的出现率还是比较低的.
利用JAVA ClassLoader ,JRE自身特点,产生代码5.
(有兴趣的可以看看IBM和sun网写的java classLoader文章和有关类加载顺序)
代码5:
可以带父类的://会生成文件Singleton.class和Singleton$Instance.class
示例:
public class Singleton extends SuperSingleton {
//内部类,将只被装载一次.
private static class Instance {
//final关键字
static final SuperSingleton instance = new Singleton();
}
private Singleton() {
}
public static SuperSingleton getInstance() {
return Instance.instance;
}
//暂未使用Singleton instance
private static Singleton instance;
public void doA() {
}
public void doB() {
}
//危险代码.
protected void finalize(){
//置空操作
}
}
代码5是对于单线程或多线程都是可行的.
更符合类间关系的原则,对于对象A,A要么只创建B类,要么只使用B类的方法,不要二者兼之.
然后试试对代码5的使用感受吧.
注:另外针对从代码2到代码5,在ECLIPSE下是可以自己创建代码模板,供生成代码2到代码5.
在此不提供,让大家自己熟悉代码.
模板位置:eclipse->windows->preferrence->java->editors->templates->...
最后快捷键:ALT+/
参考资料:
1. book: design pattern explains.
2. books and papers : java memory model
3. java class loader
http://java.sun.com/developer/technicalArticles/Networking/classloaders/
http://download.oracle.com/javase/tutorial/ext/basics/load.html
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/?ca=drs-tp4608
https://docs.google.com/viewer?a=v&q=cache:XhllCyQFTS0J:www.freejavaguide.com/jcl.pdf+java+classloader+ibm&hl=zh-CN&pid=bl&srcid=ADGEESiOaJjb2W-ilYRra4XOQEV4xbG1risXhRXdxtY0qYnhryvduOsLQapuQDdh1-A8frGy0cAqGNMV4vt8x-q_TLAoqgOKWpA1dlbn-6l_Jm3z2I6AvOLiXCztNafc_dx72C1hLzz0&sig=AHIEtbStlkg0vB89MsPd_YQQgHnpcrKn7A&pli=1