单例模式
- 要解决的问题:确保一个类只有一个实例,且能获得它(全局访问点)。
- 核心思想:所以构造器private,getInstance方法public
- 会遇到的问题:加载顺序(懒汉式,饿汉式),多线程问题(synchronized,双重校验锁),注册式单例(枚举,容器),单例被破坏(反射,序列化/反序列化)
- 应用举例:J2EE中的ServletContext,ServletContextConfig,Spring中的ApplicationContext,Bean(默认方式),数据库连接池等
根据加载顺序
饿汉式
- 在类加载时就立即初始化,并且创建单例对象,线程还没出现前就实例化,不会出现线程安全问题。
- 优点:不用加任何锁,执行效率高
- 缺点:不管用不用都会初始化,可能造成内存浪费。
public class HungrySingleton {
//私有静态常量实例
private static final HungrySingleton INSTANCE = new HungrySingleton();
//构造器私有
private HungrySingleton(){}
//getInstance公开
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
懒汉式
- 被外部类调用时才会加载(解决饿汉式内存浪费的问题)
- 存在线程安全隐患
// 简单懒汉式单例模式
public class LazySimpleSingleton {
//构造器私有
private LazySimpleSingleton() {}
// 私有静态实例
private static LazySimpleSingleton lazy = null;
// getInstance方法公开
public static LazySimpleSingleton getInstance(){
if(lazy==null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
- 排除线程安全问题,添加sychorinized关键字
// 线程安全的懒汉式单例模式
public calss LazySimpleSingleton {
private LazySimpleSingleton() {}
private static LazySimpleSingleton instance = null;
public synchronized static LazySimpleSingleton getInstance() {
if(instance==null) {
instance = new LazySimpleSingleton();
}
}
}
- 在加锁的情况下,虽然解决了线程安全问题,但线程数量较多时,会造成程序性能下降。
- 兼顾线程安全又提高程序性能(双重校验锁)。
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance = null;
private LazyDoubleCheckSingleton(){}
//不在方法上加锁
public static LazyDoubleCheckSingleton getInstance() {
if(instance==null){
synchronized (LazyDoubleCheckSingleton.class){
if(instance==null){
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
- 相比于线程同步的懒汉式,双重校验锁的机制使得性能有所提升,但加锁多多少少会影响性能。
- 静态内部类的方法,兼顾饿汉式单例模式内存浪费问题,和synchronized的性能浪费问题。
// 静态内部类的写法
public class InnerClassSingleton {
private static class InnerClassSingletonHolder{
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static final InnerClassSingleton getInstance() {
return InnerClassSingletonHolder.INSTANCE;
}
}
- 内部类会在方法调用之前初始化,避免了线程安全问题和性能浪费。
破坏单例
反射破坏单例
- private关键字不能限制反射的使用,当分别用getInstance和反射来调用其构造方法时,会得到两个不同的实例。
public class ReflectionBreakSingleton {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("Singleton.DCLSingleton");
Constructor c = clazz.getDeclaredConstructor();
c.setAccessible(true);
DCLSingleton s1 = (DCLSingleton) c.newInstance();
DCLSingleton s2 = DCLSingleton.getInstance();
System.out.println(s1);
System.out.println(s2);
}
}
- 需要在单例模式中对反射加以限制
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {
if(LazyHolder.INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
private static final LazyInnerClassSingleton getInstance() {
return LazuHolder.INSTANCE;
}
private static class LazyHolder{
private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
}
}
— 待续