单例模式

单例模式

定义:保证一个类仅有一个实例,并提供一个全局访问点

类型:创建型

适用场景

想确保任何情况下都绝对只有一个实例

优点

  • 在内存里只有一个实例,减少了内存开销
  • 可以避免对资源的多重占用
  • 设置全局访问点,严格控制访问

缺点

  • 没有接口,扩展困难

重点

  • 私有构造器
  • 线程安全
  • 延迟加载
  • 序列化和反序列化安全
  • 反射

懒汉式

使用的时候才初始化

public class LazySingleton {
    // 初始化不创建
    private static LazySingleton lazySingleton = null;
    // 私有构造器
    private LazySingleton() {
    }
    // 获取lazysingleton对象的方法
    public synchronized static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return  lazySingleton;
    }
}

在多线程下会有问题,在run模式下打印出来结果是同一个对象是因为结果被最后一个线程覆盖了,是最新的对象,在debug模式下就会看到两个实例对象了。

progrem end
Thread-0singleton.LazySingleton@42337127
Thread-1singleton.LazySingleton@4233712

改进

使用synchronized同步锁

public synchronized static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }


// synchronized类代码块,与上面的写法效果一致
public static LazySingleton getInstance(){
        synchronized (LazySingleton.class){
            if(lazySingleton == null){
                lazySingleton = new LazySingleton();
            }
        }
        return  lazySingleton;
    }

 

在方法上加synchronized同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。

继续改进,双重检查


public static LazyDoubleCheckSingleton getInstance(){
        if(lazyDoubleCheckSingleton == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazyDoubleCheckSingleton == null){
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }

 

使用双重检查进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。

 

静态内部类

懒加载

public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }

    private StaticInnerClassSingleton() {
    }
}

 

只有一个线程去获得InnerClass这个静态内部类的初始化锁去初始化

只有一个线程对内部类进行类加载

 

饿汉式

在类加载的时候就创建,只会有一个线程进行类加载

public class HungrySingleton{

    private final static HungrySingleton hungrySingleton=new HungrySingleton();

    private HungrySingleton(){
    
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

}

声明为final必须在类加载的时候就初始化好,被定义为static字段或在静态代码块中初始化

public class HungrySingleton{

    private final static HungrySingleton hungrySingleton;
    static{
          hungrySingleton = new HungrySingleton();
      }

    private HungrySingleton(){
    
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

}

序列化破坏单例模式

将对象序列化到文件中,然后再从文件中取出来,这两个对象还是一个对象吗?

先让对象实现Serializable接口

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
       
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

}

 

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        HungrySingleton newInstance = (HungrySingleton) ois.readObject();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}
com.geely.design.pattern.creational.singleton.HungrySingleton@1973e9b com.geely.design.pattern.creational.singleton.HungrySingleton@1663380 
false

发现序列化,然后反序列化得到的不是同一个对象为了解决这个问题,需要再添加一个方法

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
       
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
  
    private Object readResolve(){
        return hungrySingleton;
    }

}
com.geely.design.pattern.creational.singleton.HungrySingleton@1973e9b com.geely.design.pattern.creational.singleton.HungrySingleton@1973e9b 
true

添加了一个readResolve方法,发现问题解决了。

解决反射攻击

构造器是私有的,但可以通过反射把构造器的权限打开

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
        HungrySingleton instance = HungrySingleton.getInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);

    }
}
com.geely.design.pattern.creational.singleton.HungrySingleton@8cf4c6
com.geely.design.pattern.creational.singleton.HungrySingleton@edcd21
false

在类加载时创建对象,可以这样修改其构造方法添加异常

饿汉式和内部静态类的单例都可以这样修改

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

}

对于懒汉式这种不是在类加载时创建对象的,无法避免反射攻击

因为懒汉式避免多线程的时候是在方法中避免的,但用反射的时候根本不用通过这个方法创建,想啥时候创建啥时候创建。但在类加载的时候有class锁避免多线程,所以可以避免反射攻击。

枚举

不受序列化影响

不能被反射

public enum EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getInstance(){
        return INSTANCE;
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值