欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

设计模式系列之单例模式

程序员文章站 2022-03-26 22:55:12
单例模式的实现在java中实现单例模式的方法有很多,比如饿汉式、懒汉式、静态内部类、枚举类型。单例模式的核心就是只产生一个单例,外部无法通过new来创造新的类对象,也无法重复获得不同的类对象,只可根据该类暴露的一个getinstance()方法来获取初始化以后的单例对象。以下为单例模式的具体实现:1、饿汉式public class HungrySingletonTest { public static void main(String[] args) { HungrySin...

单例模式的实现

在java中实现单例模式的方法有很多,比如饿汉式、懒汉式、静态内部类、枚举类型。

单例模式的核心就是只产生一个单例,外部无法通过new来创造新的类对象,也无法重复获得不同的类对象,只可根据该类暴露的一个getinstance()方法来获取初始化以后的单例对象。

以下为单例模式的具体实现:

1、饿汉式

public class HungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton instance1 = HungrySingleton.getInstance();
        HungrySingleton instance2 = HungrySingleton.getInstance();
        System.out.println(instance1==instance2);
    }
}

class HungrySingleton{
    private static HungrySingleton instance = new HungrySingleton();
    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return instance;
    }
}

标准饿汉式实现,通过private私有的无参构造来防止外部来new这个对象,饿汉式会在一开始就在静态代码块初始化一个instance类对象,需要的时候调用getInstance()方法取出instance对象即可。

2、懒汉式

public class LazySingletonTest {
    public static void main(String[] args) {
        new Thread(() -> {
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();

        new Thread(() -> {
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();
    }
}

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();
                }
            }
        }

        return instance;
    }
}

这种方法为标准的DCL懒汉式,一开始不初始化类对象,当使用的时候临时创建类对象,为防止重复创建需对对象进行加锁,为了防止JVM的底层指令重排机制造成多线程对对象重复进行创建,使用volatile关键字来防止指令重排。

3、静态内部类

public class InnerClassSingletonTest {
    public static void main(String[] args){

        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
        InnerClassSingleton instance2 = InnerClassSingleton.getInstance();
        System.out.println(instance1==instance2);

    }
}

class InnerClassSingleton {

    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }

}

这种方法通过类中的私有内部类进行创建,只对外提供了一个公共方法getInstance,在初始化的时候调用私有类,获取属性时创建单例。

4、枚举

public enum EnumSingleton {
    INSTANCE;

    public void print(){
        System.out.println(this.hashCode());
    }
}

class EnumTest{
    public static void main(String[] args){
        EnumSingleton.INSTANCE.print();
    }
}

利用反射破坏单例模式

其实除了枚举方式实现以外,其他类型都可以通过反射来破坏单例。

下面以静态内部类进行举例:

测试反射破坏单例

public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();

        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        System.out.println(innerClassSingleton==instance);

    }
}

设计模式系列之单例模式

因此可以通过在初始化构造函数中判断单例是否存在,存在则抛出异常进行处理。

class InnerClassSingleton{

    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }

}

设计模式系列之单例模式

解决单例模式的序列化问题

还是以静态内部类举例,此时我们希望将类序列化保存在文件中。

class InnerClassSingleton implements Serializable {
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

测试序列化然后反序列化以后是否还是原来的单例。

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

        InnerClassSingleton instance = InnerClassSingleton.getInstance();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerializableTest"));
        oos.writeObject(instance);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SerializableTest"));
        InnerClassSingleton innerClassSingleton = (InnerClassSingleton)ois.readObject();
        ois.close();
        
        System.out.println(innerClassSingleton==instance);

    }
}

设计模式系列之单例模式

此时可得两者是不同的单例,通过查看Serializable类寻找到解决方法

设计模式系列之单例模式

通过在类中实现Object readResolve() throws ObjectStreamException来解决序列化问题。此时增加以下代码:

Object readResolve() throws ObjectStreamException{
    return InnerClassHolder.instance;
}

再次测试,若出现如下报错

设计模式系列之单例模式

则需要添加序列化的一个版本号,同样查看Serializable类,可以发现

设计模式系列之单例模式

如不添加版本号会根据类中的数据自动生成,如果对类进行了修改,则存进的和取出的版本号不相同,则报错,如果自己添加则会根据添加的版本号直接获取数据。

static final long serialVersionUID = 42L;

最终单例类如下:

class InnerClassSingleton implements Serializable {
    static final long serialVersionUID = 42L;

    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }

    Object readResolve() throws ObjectStreamException{
        return InnerClassHolder.instance;
    }

}

再次测试

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

        InnerClassSingleton instance = InnerClassSingleton.getInstance();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerializableTest"));
        oos.writeObject(instance);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SerializableTest"));
        InnerClassSingleton innerClassSingleton = (InnerClassSingleton)ois.readObject();
        ois.close();

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

    }
}

设计模式系列之单例模式

验证枚举的安全性

我们来试图尝试用反射破坏枚举

public enum EnumSingleton {
    INSTANCE;

    public void print(){
        System.out.println(this.hashCode());
    }
}

class EnumTest{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        declaredConstructor.newInstance("INSTANCE",0);
    }
}

设计模式系列之单例模式

由此可以验证反射无法对枚举类型进行修改,查看反射的newInstance方法

设计模式系列之单例模式

如图可以得到相同的结论。

由此可知枚举类型是相对安全的一种实现单例的方式。

本文地址:https://blog.csdn.net/qq_40359381/article/details/112236367