单例模式的意图
只允许创建一个实例,并提供全局访问点。
饿汉式
public class HungryMan{
private HungryMan(){}
private static final HUNGRY_MAN = new HungryMan();
public static HungryMan getInstance(){
return HUNGRY_MAN;
}
}
饿汉式的问题是,如果对象占用资源比较重,并且系统只有在某些条件下才会使用该对象时就会出现不必要的资源开销,此时饿汉式就不太合理。
懒汉式
解决饿汉式可能存在的资源浪费问题
public class LazyMan {
private LazyMan(){}
private static volatile LazyMan LAZY_MAN;
public static LazyMan getInstance(){
if (LAZY_MAN == null) {
synchronized (LazyMan.class){
if (LAZY_MAN == null) {
LAZY_MAN = new LazyMan();
}
}
}
return LAZY_MAN;
}
}
注意:synchronized 加锁是为了解决并发情况下可能出现重复实例化的问题;volatile 关键字是避免极端情况下new语句发生指令重排导致重复实例化问题;
使用静态内部类
public class LasyMan2 {
private LasyMan2() {
System.out.println("init...");
}
public static LasyMan2 getInstance() {
System.out.println("before init..");
return LazyManHolder.LASY_MAN;//显示引用该变量时会触发变量初始化
}
private static class LazyManHolder {
private static final LasyMan2 LASY_MAN = new LasyMan2();
}
}
但以上方式都无法避免反射绕开单例模式。
使用枚举
public enum LazyManEnum {
LAZY_CAT("cat"),
LAZY_DOG("dog"),
;
private String name;
LazyManEnum(String name){
this.name = name;
System.out.println(name+" init...");
}
}
实现单例最安全的方式是使用枚举。因为JDK中已经限制反射无法实例化枚举:
//from java.lang.reflect.Constructor
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//枚举无法实例化
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
通过反编译查看枚举的实例化过程,可以发现是通过静态代码块进行初始化,其本质就是饿汉式。
public final class LazyManEnum extends Enum
{
public static LazyManEnum[] values()
{
return (LazyManEnum[])$VALUES.clone();
}
public static LazyManEnum valueOf(String name)
{
return (LazyManEnum)Enum.valueOf(com/cp/design/single/LazyManEnum, name);
}
private LazyManEnum(String s, int i, String name)
{
super(s, i);
this.name = name;
System.out.println((new StringBuilder()).append(name).append(" init...").toString());
}
public static final LazyManEnum LAZY_CAT;
public static final LazyManEnum LAZY_DOG;
private String name;
private static final LazyManEnum $VALUES[];
static //此处实例化对象
{
LAZY_CAT = new LazyManEnum("LAZY_CAT", 0, "cat");
LAZY_DOG = new LazyManEnum("LAZY_DOG", 1, "dog");
$VALUES = (new LazyManEnum[] {
LAZY_CAT, LAZY_DOG
});
}
}
饿汉式真的饿吗?
之所以称为饿汉式是因为系统还没有使用就提前被初始化了,那上文的饿汉式单例什么时候被初始化的?其实在调用getInstance()方法时才会触发初始化。理清类的加载和对象实例化过程后,只要避开触发单例实例的操作就可以避免提前初始化。