单例模式(两种)

目录

一,概念

二,应用实例

三,饿汉模式

四,懒汉模式(单线程)

五,懒汉模式(多线程且效率低)

六,懒汉模式(双重校验锁)

七,破坏单例模式

八,总结


一,概念

  • 单例模式是指在内存中只会创建且仅创建一次对象的设计模式
  • 在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象

 

二,应用实例

在jdbc操作时,使用了DataSource(数据库连接池)

三,饿汉模式

  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
public class Singleton {
    //在类加载时就已经创建好,不存在线程安全和并发问题
    private static Singleton instance  = new Singleton();

    private static Singleton getInstance(){
        return instance;
    }
}

四,懒汉模式(单线程)

  • 懒汉式:在真正需要使用对象时才去创建该单例类对象
public class SingletonLazy {
    //类加载时不创建,在调用方法时才实例化一个单例对象
    //线程不安全
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
}

五,懒汉模式(多线程且效率低)

public class SingletonLazy {
    //类加载时不创建,在调用方法时才实例化一个单例对象
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        //线程安全了,但是每次获取对象都需要先获取锁,并发性能非常地差
        //极端情况下,可能会出现卡顿现象
        synchronized (SingletonLazy.class) {
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }
}

六,懒汉模式(双重校验锁)

public class SingletonLazy {
    //类加载时不创建,在调用方法时才实例化一个单例对象
    private static volatile SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        //线程安全了,但是每次获取对象都需要先获取锁,并发性能非常地差
        //极端情况下,可能会出现卡顿现象
        if(instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

关于volatile锁的作用:防止指令重排序

创建一个对象,在JVM中会经过三步:

(1)为instance分配内存空间

(2)初始化instance对象

(3)将instance指向分配好的内存空间

指令重排序是指:

  1. JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能
  2. 使用volatile关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换
  3. 使用volatile关键字修饰的变量,可以保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。

七,破坏单例模式

无论是完美的懒汉式还是饿汉式,终究敌不过反射和序列化,它们俩都可以把单例对象破坏掉(产生多个对象)。

1:利用反射破坏单例模式(利用反射,强制访问类的私有构造器,去创建另一个对象        )

public static void main(String[] args) {
    // 获取类的显式构造器
    Constructor<Singleton> construct = Singleton.class.getDeclaredConstructor();
    // 可访问私有构造器
    construct.setAccessible(true); 
    // 利用反射构造新对象
    Singleton obj1 = construct.newInstance(); 
    // 通过正常方式获取单例对象
    Singleton obj2 = Singleton.getInstance(); 
    System.out.println(obj1 == obj2); // false
}

2:利用序列化与反序列化破坏单例模式

public static void main(String[] args) {
    // 创建输出流
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));
    // 将单例对象写到文件中
    oos.writeObject(Singleton.getInstance());
    // 从文件中读取单例对象
    File file = new File("Singleton.file");
    ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
    Singleton newInstance = (Singleton) ois.readObject();
    // 判断是否是同一个对象
    System.out.println(newInstance == Singleton.getInstance()); // false
}

在 JDK1.5 后,使用 Java 语言实现单例模式的方式又多了一种:枚举

public enum Singleton {
    INSTANCE;
    
    public void doSomething() {
        System.out.println("这是枚举类型的单例模式!");
    }
}

优势1:代码对比饿汉式与懒汉式来说,更加地简洁

优势2:它不需要做任何额外的操作去保证对象单一性与线程安全性

优势3:使用枚举可以防止调用者使用反射序列化与反序列化机制强制生成多个单例对象,破坏单例模式

八,总结

(1)单例模式常见的写法有两种:懒汉式、饿汉式

(2)懒汉式:在需要用到对象时才实例化对象,正确的实现方式是:双重校验锁,解决了并发安全和性能低下问题

(3)饿汉式:在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。

(4)在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;

(5)如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题

(6)为了防止多线程环境下,因为指令重排序导致变量报异常,需要在单例对象上添加volatile关键字防止指令重排序

(7)最优雅的实现方式是使用枚举,其代码精简,没有线程安全问题,且 Enum 类内部防止反射和反序列化时破坏单例。

猜你喜欢

转载自blog.csdn.net/qq_54773998/article/details/123869414