1)SimpleSingleton
package com.panny;
public class SimpleSingleton {
/*
* 这种单例的实现方式非常简单,而且十分可靠。
* 它唯一的不足是无法对instance实例做延迟加载。假如单例的创建过程很慢,而由于instance成员变量
* 是static定义的,因此在JVM加载单例时,单例对象就会被立即建立,如果此时,这个单例类在系统中还扮演着
* 其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到。
* */
private static SimpleSingleton inistance = new SimpleSingleton();
private SimpleSingleton() {
System.out.println("Creating SimpleSingleton..."); // 创建单例的过程可能会比较慢
}
public static SimpleSingleton getInstence() {
return inistance;
}
public static void greeting() {
System.out.println("Hello, SimpleSingleton...");
}
}2) lazySingleton
package com.panny;
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
System.out.println("Creating LazySingleton...");
}
/*再次需要特别注意:getInstance()方法必须是同步的。
否则在多线程环境下,当线程1正新建单例时,完成赋值操作前,
线程2可能判断instance为null,故线程2也将新建单例,
而导致多个实例被创建, 故同步关键字是必须的。*/
public static synchronized LazySingleton getInstance() {
if(null == instance) {
instance = new LazySingleton();
}
return instance;
}
public static void greeting() {
System.out.println("Hello, LazySingleton...");
}
}3) StaticSingleton
package com.panny;
public class StaticSingleton {
/*在这个实现中,单例模式使用内部类来维护单例的实例,
* 当StaticSingleton被加载时,其内部类不会被初始化,
* 故可以确保当StaticSingleton被载入JVM时,不会初始化单例类,
* 而当getInstence()方法被调用时,才会加载内部类(StaticSingletonHolder),
* 从而初始化instance。同时,由于实例的建立是在类加载时完成,故天生对多线程友好,
* getInstence()方法也不需要使用synchronized关键字。*/
private StaticSingleton() {
System.out.println("Creating StaticSingleton...");
}
private static class StaticSingletonHolder {
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstence() {
return StaticSingletonHolder.instance;
}
}4) SerSingleton
package com.panny;
public class SerSingleton implements java.io.Serializable {
private static SerSingleton instance = new SerSingleton();
private SerSingleton() {
System.out.println("Creating SerSingleton...");
}
public static SerSingleton getInstance() {
return instance;
}
private Object readResolve() {
return instance;
}
}5) MyTest
package com.panny;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// SimpleSingleton.greeting();
/*
* Creating SimpleSingleton...
* Hello, SimpleSingleton...
*
* 可以看到,虽然此时并没有使用单例类,但它还是被创建出来,这也许是开发人员不愿意见到的。
* 为了解决这个问题,并以此提高系统在相关函数调用时的反应速度,就需要引入延迟加载机制:LazySingleton
*/
// LazySingleton.greeting(); // Hello, LazySingleton...
/*
* 虽然LazySingleton实现了延迟加载的功能,但和SimpleSingleton相比,
* 它引入了synchronized关键字,因此在多线程环境中,它的耗时要远远大于SimpleSingleton,
* 以下测试码说明了这个问题:
* */
class MyThread implements Runnable {
String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
long beginTime = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
// SimpleSingleton.getInstence();
/*T1 spend: 7
T3 spend: 12
T4 spend: 12
T5 spend: 11
T2 spend: 12*/
LazySingleton.getInstance();
/*T1 spend: 259
T3 spend: 266
T5 spend: 269
T4 spend: 272
T2 spend: 273*/
// StaticSingleton.getInstence();
/*T5 spend: 6
T1 spend: 12
T4 spend: 11
T2 spend: 7
T3 spend: 11*/
}
System.out.println(this.name + " spend: " + (System.currentTimeMillis() - beginTime));
}
}
Thread t1 = new Thread(new MyThread("T1"));
t1.start();
Thread t2 = new Thread(new MyThread("T2"));
t2.start();
Thread t3 = new Thread(new MyThread("T3"));
t3.start();
Thread t4 = new Thread(new MyThread("T4"));
t4.start();
Thread t5 = new Thread(new MyThread("T5"));
t5.start();
/*开启5个线程同时运行
* 两种类型性能至少相差2个数量级,
* 为了使用延迟加载引入的synchronized关键字反而降低了系统性能,是不是有些得不偿失呢?
* 为了解决这个问题,需要对其进行改进:StaticSingleton
* 查看测试码,实现了性能改进。*/
/*通常情况下,用以上方式实现的单例已经可以确保在系统中只存在唯一实例了。
* 但仍然有例外的情况可能导致系统生成多个实例,比如:1)在代码中通过反射机制,强行调用
* 单例类的私有构造函数,生成多个单例;2)通过java.io.Serializable序列化单例类生成多个单例。
* 第一种方式太极端,一般不会遇见,以下尝试解决第二种方式产生的问题:SerSingleton */
SerSingleton s0 = SerSingleton.getInstance();
FileOutputStream fos = new FileOutputStream("SerSingleton.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s0);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SerSingleton.txt"));
SerSingleton s1 = (SerSingleton) ois.readObject();
ois.close();
System.out.println(s0 == s1);
/*当去掉SerSingleton中的readResolve()方法时,s0 == s1为false,
* 说明s0和s1分别引用了不同的实例,即在反序列化后生成了多个实例对象。
* 而当恢复SerSingleton中的readResolve()方法时,s0 == s1为true,
* 说明即便经过了反序列化,仍然保持了单例的特征。
* 实际上,在实现了私有的readResolve()方法后,readObject()已经形同虚设,
* 它直接使用readResolve()替换了原本的返回值,从而形式上构造了单例。
* 注意:序列化和反序列化可能会破坏单例,一般来说,对单例进行序列化和反序列化
* 的场景不多,但如果存在,就要多加注意。*/
}
}鸣谢:感谢葛一鸣老师的《Java程序性能与优化》,从中我学习了许多珍贵的编程技巧与经验。
本文出自 “好寂寞先生” 博客,请务必保留此出处http://pannyhjm.blog.51cto.com/4473736/1384382
Java设计模式之一:单例模式,布布扣,bubuko.com
原文:http://pannyhjm.blog.51cto.com/4473736/1384382