java单列模式的几种实现方式

单列模式(SIngleton pattern)是java中最基础的一种设计模式,他的作用是保证一个类仅有一个实例,并且提供一个访问它的全局访问点,避免重复创建对象,节省系统资源。以下通过五种方式来实现,可根据不同的场景选用。

一、饿汉式

public class Singleton {
    //类加载时就创建本类的对象
    private static Singleton instance = new Singleton();
    //构造器私有化,使外部不能通过new来创建对象
    private Singleton(){}
    //外部可通过该方法来获取唯一实例
    public static Singleton getInstance(){
        return instance;
    }
}

之所以称之为饿汉式,可以理解为:饿了,马上就要吃,在类加载的时候就创建对象,用时直接取就行。

缺点:在还不需要此类的实例的时候就已经创建好了,没有起到 lazy loading 的效果。

优点:简单、安全可靠

二、懒汉式

1、线程不安全
public class Singleton {
    //用静态变量来记录本类实例
    private static Singleton instance;
    //获取实例
    public static Singleton getInstance(){
        if(instance == null){
        	instance = new Singleton();
    	}
        return instance;
    }
    //构造器私有化
    private Singleton(){}
  
}

可以理解为:很懒、需要用时再创建,当一个对象使用频率不是很高、占用的内存特别大,饿汉式就明显不合适了,这时就需要懒汉式这种懒加载的思想,等到程序需要实例的时候再创建。上面这种实现方式是存在线不程安全问题,在并发获取实例的时候,可能会存在构建多个实例的情况,通过下面的改进可以实现线程安全:

2、线程安全
public class Singleton {
    private static volatile Singleton instance;
    //构造器私有化,禁止外部通过new创建实例
    private Singleton(){}
    
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        
        return instance;
    }
}

与上面的相比,这里使用了双重效验的方式,对懒汉式线程安全化,通过加锁,可以保证同时只有一个线程走到第二个判空代码中去,这样保证了只创建 一个实例。这里还用到了volatile关键字来修饰singleton,其最关键的作用是防止指令重排。

三、静态内部类式

public class Singleton {
    
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

通过这种静态内部类的方式实现的单例模式是线程安全的,静态的内部类在Singleton加载的时候是不会加载的。在调用getInstance时才会创建Singleton的实例,起到了懒加载的作用。

以上方式实现单例模式看起来已经完美,但是还会存在一些安全问题,即反射攻击、反序列化攻击,下面就通过这两种方式来获取新的实例,并验证原来的实列和新的实例是否是一个实例。

反射获取新实例

public class Singleton {
    
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }

    public static void main(String[] args) {

            Singleton singleton = Singleton.getInstance();
            Constructor<Singleton> constructor;
            try {
                constructor = Singleton.class.getDeclaredConstructor();
                constructor.setAccessible(true);
                Singleton newSingleton = constructor.newInstance();
                System.out.println(singleton == newSingleton);
            }catch (Exception e){

            }

        }
}

在这里插入图片描述
通过结果可以看出这两个实例不是同一个实例,这和我们的单例模式不符。

除了反射获取还可以通过反序列化的方式获取到新的实例:

反序列化方式获取新实例

public class Singleton implements Serializable {
    
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
    
    public static void main(String[] args) {

        Singleton singleton = Singleton.getInstance();

        try {

            ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(new File("./Singleton.txt")));
            //把对象写入磁盘
            oos.writeObject(singleton);
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Singleton.txt")));
            //从磁盘中读取对象
            Singleton newSingleton = (Singleton)ois.readObject();
			//比较
            System.out.println(singleton == newSingleton);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
和反射获取的结果是一样的,下面通过枚举的方式实现解决上面这两个问题。

四、枚举方式

public enum EnumSingleton {
    
    INSTANCE;

    private EnumSingleton(){
        System.out.println("init");
    }

    public void sayHello(){
        System.out.println("hello");
    }

    public static void main(String[] args) {
        for (int i =0;i<5;i++){
            EnumSingleton.INSTANCE.sayHello();
        }

    }

}

在这里插入图片描述
结果可以看出实际只初始化了一次,这种方式也可以实现单例。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。

总结

以上通过了多中方式实现了单例模式,分析其的利弊,可根据场景选用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值