【设计模式】创建型模式之单例设计模式

【设计模式】创建型模式之单例设计模式

1. 概述

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。


2. 结构

单例模式的主要有以下角色:

  • 单例类。只能创建一个实例的类
  • 访问类。使用单例类

3. 实现

单例设计模式的类别及实现方式:

  1. 饿汉式:类加载阶段就会导致该单实例对象被创建。
    • 静态变量方式实现
    • 静态代码块方式实现
    • 枚举方式实现
  2. 懒汉式:只有首次使用该对象的时候才会创建。
    • 线程不安全方式实现
    • 线程安全方式实现
    • 双重检查锁方式实现
    • 静态内部类方式实现
    • 枚举方式实现

3.1 饿汉式:静态变量方式实现

//静态变量方式实现
public class Singleton {
    
    

    //私有
    private Singleton() {
    
    
    }

    //私有
    private static Singleton instance = new Singleton();

    //共有
    public static Singleton getInstance() {
    
    
        return instance;
    }

}

测试:

创建一个测试类,编写如下方法测试该单例模式是否成功实现:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        System.out.println(instance1==instance2);//true
    }
}

运行结果:true

结果为true,说明创建单例模式成功。之后不再测试。

注:该方式创建的单例对象instance是随着类的加载而创建的,如果该对象足够大且一直没有使用就会造成内存上的浪费。


3.2 饿汉式:静态代码块方式实现

//静态代码块方式实现
public class Singleton {
    
    

    //私有
    private static Singleton singleton = null;

    static {
    
    
        singleton = new Singleton();
    }

    //私有
    private Singleton() {
    
    
    }

    //公有
    public static Singleton getInstance() {
    
    
        return singleton;
    }
}

静态变量方式实现一样,都会造成内存上的浪费。


3.3 饿汉式:枚举方式实现(推荐)

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

public enum Singleton {
    
    
    INSTANCE;
}

3.4 懒汉式:线程不安全方式实现

//线程不安全
public class Singleton {
    
    

    //私有
    private Singleton() {
    
    
    }

    //私有
    private static Singleton instance;

    //公有
    public static Singleton getInstance() {
    
    
        //如果对象为空则创建对象赋值后返回
        //如果对象不为空则直接返回
        return instance == null ? (instance = new Singleton()) : instance;
    }
}

注:当第一次调用 getInstance() 方法的时候才会去创建单例对象,这样就实现了懒加载的效果。但是当多线程并发执行该方法时,可能会出现同时多个线程进入了该方法并且 instance==null 判定为true,这样就会创建多个对象,违背了单例模式的初衷。


3.5 懒汉式:线程安全方式实现

//线程不安全
public class Singleton {
    
    

    //私有
    private Singleton() {
    
    
    }

    //私有
    private static Singleton instance;

    //公有
    public static synchronized Singleton getInstance() {
    
    
        //如果对象为空则创建对象赋值后返回
        //如果对象不为空则直接返回
        return instance == null ? (instance = new Singleton()) : instance;
    }
}

注:该实现与线程不安全方式实现差别只在于是否在 getInstance() 方法上添加 synchronized 关键字。加了 synchronized 关键字后线程就会串行执行 getInstance() 方法。这样就解决了线程安全问题,不过该方法的执行效果特别低。


3.6 懒汉式:双重检查锁方式实现(推荐)

由于懒汉式的 getInstance() 方法是读多写少(读指获得实例对象,写指创建单例对象)。那么我们可以调整加锁的时机,调整之后的模式也叫双重检查锁模式。

//双重检查锁
public class Singleton {
    
    

    private Singleton() {
    
    
    }

    private static Singleton instance;

    public static Singleton getInstance() {
    
    
        //第一次判断
        if (instance == null) {
    
    
            synchronized (Singleton.class) {
    
    
                //第二次判断
                if (instance == null) {
    
    
                    //创建单例对象
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为什么要双重判断呢?我们先假设只有一次判断:

在多线程情况下,可能出现多个线程都通过第一次的判断,然后通过判断的若干线程(A,B,C)去抢锁,A抢到锁的去创建了单例对象后释放了锁,B又抢到了锁然后又创建了一个单例对象,最后C也抢到了锁,这样总共就创建了三个单例对象了,显然是不合理的。

所以,我们需要双重判断,在A创建了单例对象后,B进入同步代码块再次判断对象是否创建,发现对象已创建,则不再创建。这样就只创建了一个单例对象。

注:双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,但实际却存在问题,在多线程情况下可能会出现空指针异常,原因是JVM在实例化对象的时候会对代码进行优化和指令重排序操作,想要解决双重检查锁带来的空指针异常,我们可以使用 volatile 关键字,该关键字能够在指令间加入内存屏障,避免指令重排序,保证指令的有序性。

修改如上代码,只需在对应变量添加 volatile 关键字即可:

private static volatile Singleton instance;

注:添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。


3.7 懒汉式:静态内部类方式实现(推荐)

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 staticfinal 修饰,保证只被实例化一次并且外部不能重新为其赋值,并且严格保证实例化顺序。

public class Singleton {
    
    

    private Singleton() {
    
    
    }

    public static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

说明:

第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用 getInstance() 方法,JVM加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。


4. 破坏单例模式

有两种方法可以破坏除枚举方式实现外的所有单例模式:

  1. 序列化
  2. 反射

4.1 序列化

针对静态内部类方式实现举例,为其实现序列化接口:

public class Singleton implements Serializable {
    
    

    private Singleton() {
    
    
    }

    //静态内部类
    public static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

编写测试类:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //writeObject2File();
        readFile2Object();
        readFile2Object();
        readFile2Object();
    }

    //从文件读取数据
    public static void readFile2Object() {
    
    
        try (
                //创建对象输入流
                ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("d:\\a.txt"));
        ) {
    
    
            //读取对象
            Singleton instance = (Singleton) objectInputStream.readObject();
            System.out.println(instance);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    //将对象序列化到文件
    public static void writeObject2File() {
    
    
        //获取单例对象
        Singleton instance = Singleton.getInstance();
        try (
                //创建对象输出流
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("d:\\a.txt"));
        ) {
    
    
            //写对象
            objectOutputStream.writeObject(instance);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

在测试类中编写序列化方法和反序列方法,先执行序列化方法,然后执行反序列化方法,结果如下:

image-20230326003604814

每一次反序列化后得到的对象都不同,破坏了单例模式。


4.1.1 解决办法

Singleton 类种添加 readResolve() 方法,在反序列化时被反射调用,如果定义了这个方法,在调用 readObject() 方法时就会反射调用 readResolve() 方法;如果没有定义,就会返回新new出来的对象。

在单例类中添加 readResolve() 方法:

public class Singleton implements Serializable {
    
    

    private Singleton() {
    
    
    }

    //静态内部类
    public static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }


    //下面是为了解决序列化反序列化破解单例模式
    private Object readResolve() {
    
    
        return SingletonHolder.INSTANCE;
    }

    public static Singleton getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

再次运行,结果如下:

image-20230326150104117


4.2 反射

同样还是静态内部类实现的单例模式,我们编写一个测试类:

public class Client {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //获取Class对象
        Class<Singleton> clazz = Singleton.class;
        Constructor<Singleton> declaredConstructor = clazz.getDeclaredConstructor();
        //取消访问检查(爆破)
        declaredConstructor.setAccessible(true);
        //反射创建对象
        Singleton singleton1 = declaredConstructor.newInstance();
        Singleton singleton2 = declaredConstructor.newInstance();
        Singleton singleton3 = declaredConstructor.newInstance();
        System.out.println(singleton1);
        System.out.println(singleton2);
        System.out.println(singleton3);
    }
}

运行结果如下:

image-20230326145451473

说明反射也破坏了单例模式。


4.2.1 解决办法

反射主要就是调用无参构造器去创建单例对象,我们只需要在其构造器中编写单例逻辑即可。

public class Singleton implements Serializable {
    
    

    private static boolean flag = false;

    private Singleton() {
    
    
        synchronized (Singleton.class) {
    
    
            //判断是否第一次来,是的话就创建单例对象,不是就抛异常
            if (flag) {
    
    
                throw new RuntimeException("不能创建多个对象");
            }
            flag = true;
        }
    }

    //静态内部类
    public static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

再次运行测试类,结果如下:

image-20230326151817793


猜你喜欢

转载自blog.csdn.net/Decade_Faiz/article/details/129761019