一 定义
单例模式(Singleton Pattern):是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
隐藏其所有的构造方法
属于创建性模式
二 单例模式的几种实现方式
(一)饿汉式单例:在单例类首次加载时就创建单例
public class HungrySingleton { private static final HungrySingleton hungrySingletonm = new HungrySingleton(); private HungrySingleton(){} public HungrySingleton getInstance(){ return hungrySingletonm; } }
public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingletonm; private HungryStaticSingleton(){} static { hungrySingletonm = new HungryStaticSingleton(); } public HungryStaticSingleton getInstance(){ return hungrySingletonm; } }
上面两种方式没有本质的区别,都是在类加载时就创建类的实例
优点:执行效率高,性能高,没有任何的锁
缺点:某种情况下,可能会造成内存浪费
(二)懒汉式单例:被外部调用时才创建实例
public class LazySingleton { private static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } }
A方式 优点:不是加载类的时候就被创建,可以节约内存空间
缺点:不是线程安全的,多线程情况下,会产生多个实例
public class LazySingleton { private static LazySingleton instance; private LazySingleton(){} public static synchronized LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } }
B方式 优点:节约了内存空间,并且是线程安全的
缺点:性能很低
public class LazySingleton { private volatile static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance(){ if(instance == null){ //第一层检查:检查是否阻塞 synchronized(LazySingleton.class){ if(instance == null){ //第二层检查:检查是否要重新创建实例 instance = new LazySingleton(); //这有一个指令重排序的问题,上面的变量上加上volatile字段 } } } return instance; } }
C方式 双重检查,比较好的一种写法
优点:性能提高了,线程安全了
缺点:可读性不好,不够优雅
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton(){
if(Holder.INSTANCE != null){
throw new RuntimeException("不允许非法访问"); //防止反射破坏单例
}
}
public static StaticInnerClassSingleton getInstance(){
return Holder.INSTANCE;
}
private static class Holder{
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
}
D方式 写法优雅,利用了java本身的语言特性,性能高,避免了内存浪费
上面的几种写法都会被反射和序列化破坏单例
(三)注册式单例:将每一个实例都缓存到统一的容器中,使用唯一标示获取实例
public enum EnumSingleton { INSTANCE; public static EnumSingleton getInstance(){ return INSTANCE; } }
枚举式单例可避免序列化破坏和反射破坏
public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> ioc=new ConcurrentHashMap<String,Object>(); public static Object getBean(String className){ Object instance = null; if(!ioc.containsKey(className)){ try { instance = Class.forName(className).newInstance(); ioc.put(className,instance); } catch (Exception e) { e.printStackTrace(); } return instance; }else{ return ioc.get(className); } } }
容器式单例适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。
(四)ThreadLocal单例:保证线程内部的全局唯一,且天生线程安全。严格上说不能算作是一种单例模式,只是保证了每个线程里的实例是一个
public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalSIngleton = new ThreadLocal<ThreadLocalSingleton>(){ @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton(){} public ThreadLocalSingleton getInstance(){ return threadLocalSIngleton.get(); } }
三 学习单例模式的重点
1. 私有化构造器
2. 保证线程安全
3. 延迟加载
4. 防止序列化和反序列话破坏单例
5. 防御反射攻击单例