单例模式存在的意义在于减少不必要的内存开销,优化我们的程序性能。优化我们的代码结构,使调用方式更加方便与严谨,使封装的思想得到更好的发挥。
单例模式的写法有很多种,在这里只介绍三种个人认为比较好的写法,只介绍多线程环境下的单例模式,能扛得住复杂与高强度压力的代码才是好代码,当然,这也会使性能稍逊于普通环境下的单例模式。
方式一:
public class DBHelper {
private DBHelper() {
}
public final static DBHelper INSTANCE = new DBHelper();
public static DBHelper getInstance() {
return INSTANCE;
}
}
以上为第一种模式,从代码可以看到,我们使用了final来修饰单例,这样使得不管是不是在多线程的环境下,这个Object都只会创建一次。
方式二:
public class DBHelper {
private DBHelper() {
}
public static DBHelper getInstance() {
return SingleHolder.INSTANCE;
}
private final static class SingleHolder {
private final static DBHelper INSTANCE = new DBHelper();
}
}
从代码可以看出,我们在一个静态的Holder类创建了一个静态final的单例对象,然后在获取的时候,直接使用这个静态的Holder类访问这个静态final单例,这样就实现了单例只会被创建一次。因为静态变量只有在第一次访问类的时候才会被加载,才会被实例化,这样就保证了只有获取单例的时候才会去实例,对比上面第一种模式更优。同时,我们使用了static修饰了单例持有类,JVM的内部机制会保证一个类被加载的时候,这个加载过程是线程互斥的,而且单例还使用了final进行修饰,很好的保证了多线程环境下的安全性。
方式三:
public class DBHelper {
private static volatile DBHelper instance;
private DBHelper() {
}
public static DBHelper getInstance() {
if (instance == null) {
synchronized (DBHelper.class) {
if (instance == null) {
instance = new DBHelper();
}
}
}
return instance;
}
}
我们对单例对象使用了volatile关键字进行了修饰,然后在实例化之前会加锁,这样保证了单例对象只会创建一次,而且还是需要用到的时候创建的。
单例模式的健壮性提升:
前面我们提到了单例模式的几种写法,但是如果我们把情况考虑得再复杂一点,比如如果通过反射或者对象序列化再反序列化后我们是不是还能保证单例?答案肯定是不能保证的,因此还需要做一些增强健壮性的保护(这里以方式二(最为推荐的写法)为基础进行保护):
import java.io.Serializable;
public class DBHelper implements Serializable {
private static boolean initialized = false;
private DBHelper() {
synchronized (DBHelper.class) {
if (!initialized) {
initialized = true;
} else {
throw new RuntimeException("illegal instantiation!!!");
}
}
}
private Object readResolve() {
return getInstance();
}
public static DBHelper getInstance() {
return SingletonHolder.INSTANCE;
}
private static final class SingletonHolder {
private static final DBHelper INSTANCE = new DBHelper();
}
}
以上即为单例模式的介绍。