思考单例模式
单例就是确保一个类在全局只有一个实例
1.单例的本质
单例模式的本质:控制实例数目
单例模式是为了控制在运行期间,某些类的实例数目只能有一个。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
2.何时选用单例
建议在如下情况时,选用单例模式。
当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式。
比如:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、Session工厂等)
在开发中,很多时候有一些对象其实我们只需要一个,例如:线程池(threadpool)、缓存(cache)、默认设置、注册表(registry)、日志对象等等,这个时候把它设计为单例模式是最好的选择。
例:读取配置的类,代码中有很多地方需要读取配置,就要new很多次这个类,也就是频繁的创建和销毁;java读取配置文件是读取到内存中,如果配置文件很大,加载多次非常浪费内存空间,所以使用单例读取
单例模式的好处:
它能避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;还能够避免由于操作多个实例导致的逻辑错误。
3.单例模式的结构
- Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
4.实现
饿汉式(不推荐)
//饿汉式
//如果没有使用,也实例化了对象,浪费内存
public class HungryMan {
private static HungryMan hungryMan=new HungryMan();
private HungryMan(){
}
public static HungryMan getInstance(){
return hungryMan;
}
}
懒汉式(不推荐)
//饿汉式
//延迟加载,不会浪费内存,但出现了并发问题
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
public static LazyMan getInstance(){
return lazyMan=new LazyMan();
}
}
双重校验锁(推荐)
//双重校验锁(DCL懒汉)
//解决了并发问题,但可通过反射和反序列化进行更改。
public class DCLLazyMan {
private volatile static DCLLazyMan dclLazyMan;
private LazyMan(){
}
public static DCLLazyMan getInstance(){
if (dclLazyMan==null){
synchronized (DCLLazyMan.class){
if (dclLazyMan==null){
dclLazyMan=new DCLLazyMan(); //不是原子性操作
/**
* 实例化对象步骤:
* 1.给对象分配内存空间
* 2.执行构造器,实例化对象
* 3.把这个对象指向分配的空间
*
* 正常执行顺序是:123
* 但是为了提高效率,可能会发生指令重排,执行顺序:132
* 这时候另一个线程拿到了执行完13操作的对象,就直接返回了,但实际上这个对象还未实例化
* 所以必须保证这个对象是原子性操作
*/
}
}
}
return dclLazyMan;
}
}
静态内部类(推荐)
通过类加载器来保证对象创建的线程安全和懒加载。这种方式Singleton类被装载了,instance不会被立马初始化,因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,发现instance还没初始化,才会显示装载SingletonHolder类加载instance,延迟加载
//静态内部类
public class Singleton{
private Singleton(){
}
public static class SingletonHolder{
private static StaticInner instance=new StaticInner();
}
public static SingletonHolder getInstance(){
return InnerClass.instance;
}
}
以上四种都可以被反射破解!
例:
@Test
void contextLoads() throws Exception {
System.out.println(DCLLazyMan.getInstance());
Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
System.out.println(declaredConstructor.newInstance());
}
枚举式(最佳实践)
//枚举式
//解决了只有在使用时才进行实例化单例,线程安全,同时不能够被反序列化,以及利用反射进行破坏。
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
}
}
枚举式不会被反射破坏!
@Test
void contextLoads() throws Exception {
System.out.println(EnumSingle.getInstance());
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
System.out.println(declaredConstructor.newInstance());
}
原因: