设计模式探秘之单例模式
保证一个类只有一个实例,并提供一个访问它的全局访问点。
类加载就创建对象,结果显而易见,输出“s1等于s2”。
public class Hungry {
private Hungry() {
}
private static Hungry single = new Hungry();
public static Hungry newInstance() {
return single;
}
public static void main(String[] args) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Hungry s1 = Hungry.newInstance();
Hungry s2 = Hungry.newInstance();
if (s1 == s2) {
System.out.println("s1等于s2");
} else {
System.out.println("s1不等于s2");
}
}
}
这里还要说明一下,“饿汉式”有一丢丢的小问题,如果定义很多下面这样的占用内存的字节数组,长时间没有用到newInstance方法会造成内存浪费。
public class Hungry {
private byte[] data = new byte[1024];
...
}
public class Lazy {
private Lazy() {
}
private static Lazy single = null;
public synchronized static Lazy newInstance() {
if(single == null) {
single = new Lazy();
}
return single;
}
}
public class DoubleDetection {
private DoubleDetection() {
}
private static DoubleDetection single = null;
public static DoubleDetection getSingleton() {
if(single == null) {
synchronized (DoubleDetection.class) {
if(single == null) {
single = new DoubleDetection();
}
}
}
return single;
}
}
private volatile static DoubleDetection single = null;
该方式是“饿汉式”的改进版,这种方式很巧妙的实现了懒加载。
public class LazyHolder {
private static class Holder {
private static final LazyHolder INSTANCE = new LazyHolder();
}
private LazyHolder (){}
public static LazyHolder getInstance() {
return Holder.INSTANCE;
}
}
main方法中执行如下代码,会发现输出的居然不是单例,会不会觉得反射很邪恶呢
//获得构造器
Constructor<Lazy> con = Lazy.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Lazy singleton1 = con.newInstance();
Lazy singleton2 = con.newInstance();
//验证是否是不同对象
System.out.println(singleton1);
System.out.println(singleton2);
public enum Recommend {
INSTANCE;
public Recommend getInstance(){
return INSTANCE;
}
public String getName() {
return name;
}
private String name;
}
public class Demo {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//获得构造器
Constructor<Recommend> con2 = Recommend.class.getDeclaredConstructor();
//设置为可访问
con2.setAccessible(true);
//构造两个不同的对象
Recommend r1 = (Recommend) con2.newInstance();
Recommend r2 = (Recommend) con2.newInstance();
//验证是否是不同对象
System.out.println(r1);
System.out.println(r2);
}
}
为什么枚举能够严格的保证线程安全呢?我们看一下源码,枚举会直接抛异常
我们测试一下结果,居然是NoSuchMethodException异常,好奇怪
像下面这样改一下代码再次运行,终于出现了Cannot reflectively ...
Constructor<Recommend> con2 = Recommend.class.getDeclaredConstructor(String.class, int.class);
实际的开发中,我们不会真的主动用反射破坏单例,所以绝大多数情况下文中所列单例模式都是线程安全的。从单例模式的定义可以得出结论,一个类只有一个实例而且只能从一个全局访问点访问的情况下可以选用单例模式。