设计模式详解————单例模式

前言:

今天开始将23种设计模式梳理一遍。

概述:

单例模式是创建者模式,它的主要关注点是"怎样创建对象",它的主要特点是将对象的创建和使用分离,这样可以降低系统的耦合度,使用者不要关注对象的创建细节。

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

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

1.单例模式的结构

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

单例类:只能创建一个实例的类

访问类:使用单例类

2.单例模式的实现

单例设计模式分为两种:

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例被创建,而是首次使用该对象时才会被创建

2.1 饿汉式

2.1.1 饿汉式(静态变量)

/**
 * 饿汉式:静态成员变量
 */
public class Singleton {
    // 1.私有化构造方法
    private Singleton() {}
    
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance() {
        return instance;
    }
}

2.1.2 饿汉式(静态代码块)

// 饿汉式:静态代码块
public class Singleton {
    private Singleton() {}
    
    private static Singleton instance; // null
    static {
        instance = new Singleton();
    }
    public static Singleton getInstance() {
        return instance;
    }
}

说明:

饿汉式在类加载时就已经创建instance对象,如果后续一直不使用该对象的话,且该对象足够大的话,会造成内存的浪费..

2.1.3 饿汉式(枚举)

枚举类实现单例模式也是一个比较好的单例模式实现方式,因为枚举类是线程安全的,并且只会装载一次,设计者利用枚举这个特性去实现单例模式。

/**
 * 枚举实现方式
 */
public enum Singleton {
    INSTANCE;
}

2.2 懒汉式

2.2.1 懒汉式(线程不安全)

/**
 * 懒汉式
 */
public class Singleton {
    // 私有化构造方法
    private Singleton() {}

    private static Singleton instance; // 为null

    // 声明Singleton类型的变量Instance
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:

判断为空和实例化操作分为两步 可能带来线程不安全的问题。

2.2.2 懒汉式(线程安全)

/**
 * 懒汉式
 */
public class Singleton {
    // 私有化构造方法
    private Singleton() {}

    private static Singleton instance; // 为null

    // 声明Singleton类型的变量Instance
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:

加上synchronized同步锁使其变为一个同步方法

2.2.3 懒汉式(双重检查锁)

对于getInstance来说,大部分操作是读操作,没有必要抢锁,所以需要调整加锁的时机。

public class Singleton {
    // 私有化构造方法
    private Singleton() {
    }
    // 声明Signleton类型的变量
    private static Singleton instance;
    
    // 对外提供公共的访问方式
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) { // 锁对象为当前类的字节码对象
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

说明:

双重检查锁模式是一个比较好的单例实现模式,解决了性能,单例,线程安全问题。但其实存在问题,即在多线程的情况下,可能出现空指针的问题,出现的原因是JVM在实例化对象的时候会进行优化和指令重排序。

2.2.4 懒汉式(volatile)

使用volatile解决指令重排序的问题

public class Singleton {
    // 私有化构造方法
    private Singleton() {
    }
    // 声明Signleton类型的变量(解决指令重排序的问题)
    private static volatile Singleton instance;

    // 对外提供公共的访问方式
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) { // 锁对象为当前类的字节码对象
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

2.2.5 懒汉式(静态内部类)

静态内部类的单例模式由内部类来创建,由于JVM在加载外部类的时候,是不会记载静态内部类的,只有内部类的属性/方法被调用的时候才会被加载,并初始化静态属性,静态属性由static修饰,确保只被实例化一次,并且严格保证实例化顺序。

public class Singleton {
    
    // 私有构造方法
    private Singleton() {}
    
    // 定义一个静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

3.单例模式的破坏

3.1 序列化反序列化破坏单例模式

下面是单例模式的代码,在这我们使用的是懒汉式的静态内部类方式:


public class Singleton implements Serializable {
    // 私有构造方法
    private Singleton() {}

    // 定义一个静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

我们先将这个Singleton对象写入文件,在读取出来,看看先后读取的是不是同一个对象,用于判断是否破坏了单例模式

public class Client {
    public static void main(String[] args)  throws IOException, ClassNotFoundException {
        //writeObject2File();
        readObject2File();
        readObject2File();
    }

    public static void writeObject2File() throws IOException {
        // 获取singleton对象
        Singleton instance = Singleton.getInstance();
        // 创建对象输出流输出对象
        ObjectOutputStream ois = new ObjectOutputStream(new FileOutputStream("D://a.txt"));
        ois.writeObject(instance);
        ois.close();
    }

    public static void readObject2File() throws IOException, ClassNotFoundException {
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("D://a.txt"));
        Singleton o = (Singleton)is.readObject();
        System.out.println(o);
        is.close();
    }
}

结果如下:

可以看到两次使用readObject2File所取得对象的地址并不一样,说明单例模式已经被破坏。

3.2 通过反射破坏单例模式

Singleton还是用静态内部类的方式。这边我们使用反射分别创建两个Singleton对象,观察他们两的地址是否是相同的。

public class client {
    public static void main(String[] args) throws Exception {
        // 获取Singleton的字节码对象
        Class<Singleton> singletonClass = Singleton.class;
        // 获取无参的构造方法
        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
        // 取消访问检查
        declaredConstructor.setAccessible(true);
        // 创建Singleton对象
        Singleton singleton = declaredConstructor.newInstance();
        Singleton singleton1 = declaredConstructor.newInstance();
        System.out.println(singleton == singleton1);
    }
}

结果如下:

由此看出反射破坏了单例模式。

4. 单例模式的解决方法

4.1 对序列化的解决方法

在Singleton类中添加readResolve()方法,在反序列时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new的对象。

public class Singleton implements Serializable {
    // 私有构造方法
    private Singleton() {}

    // 定义一个静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // 当进行反序列化,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }
}

再次进行文件读取,发现两次读取的对象地址是一样的:

4.2 对反射的解决方法

反射是根据私有的构造方法去创建对象的,我们在私有的构造方法去进行相应的操作逻辑:

public class Singleton implements Serializable {

    private static boolean flag = false;

    // 私有构造方法
    private Singleton() {
        synchronized (Singleton.class) {
            if (flag) {
                throw new RuntimeException("对象不能被重复创建");
            }
            // 将flag设置为true
            flag = true;
        }

    }

    // 定义一个静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

可以看到反射想要创建两个对象时,系统会抛出异常:

补充:

jdk中的单例模式,可以看到用的是饿汉式。

最后:

今天的分享就到这里。如果我的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!(๑`・ᴗ・´๑)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值