23种设计模式 —— 单例模式【饿汉式、懒汉式、双重检查、静态内部类、枚举】

系列文章

23种设计模式 —— 设计模式目的以及遵循的七大原则
23种设计模式 —— 单例模式【饿汉式、懒汉式、双重检查、静态内部类、枚举】
23种设计模式 —— 工厂模式【简单工厂、工厂方法、抽象工厂】
23种设计模式 —— 原型模式【克隆羊、浅拷贝、深拷贝】


设计模式类型

1、创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式;

2、结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;

3、行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式。

注:不同书籍上对分类和名称可能不相同。《设计模式》有专门的书。

1、单例模式

所谓单例模式,就是采取一定的方法保证在整个软件体系中,使得某个类只能存在一个对象实例且该类只提供一个取得其对象实例的方法(静态方法)。

例如MyBatis的SessionFactory,它主要充当数据存储源的代理,负责创建Session对象。一般情况下,我们只需要用到一个SessionFactory对象,所以是单例模式。

单例模式主要有五种:饿汉式(细分为静态常量和静态代码块)、懒汉式(细分为线程不安全、同步方法、同步代码块)、双重检查、静态内部类、枚举

饿汉式

静态变量方式

public class SingletonTest01 {
    
    
    public static void main(String[] args) {
    
    
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);//结果为true,两个对象相同
    }
}
//饿汉式(静态变量)
class Singleton{
    
    
    //1. 在该类内部创建实例对象
    private static final Singleton instance = new Singleton();
    //2. 构造器私有化,使外部不能通过new创建对象
    private Singleton(){
    
    }
    //3. 对外提供唯一一个公共静态方法,来获得该类对象
    public static Singleton getInstance(){
    
    
        return instance;
    }
}

优缺点:

  1. 优点:写法简单,在类装载的时候就完成实例化,避免了线程同步问题。
  2. 缺点:在类装载时就实例化,没有达到懒加载效果(用到该实例才创建,否则不创建。如果一直没使用过这个实例,就会造成内存浪费)
  3. 结论:这种单例模式可用,但可能造成内存浪费。

静态代码块

public class SingletonTest02 {
    
    
    public static void main(String[] args) {
    
    
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);//结果为true,两个对象相同
    }
}

//饿汉式(静态代码块)
class Singleton{
    
    
    //1. 在该类内部创建实例对象
    private static Singleton instance;
    //在静态代码块里实例化
    static {
    
    
        instance = new Singleton();
    }
    
    //2. 构造器私有化,使外部不能通过new创建对象
    private Singleton(){
    
    }
    //3. 对外提供唯一一个公共静态方法,来获得该类对象
    public static Singleton getInstance(){
    
    
        return instance;
    }
}

优缺点:和静态变量方式一样,只是将类实例化放在了静态代码块,但依然是在类装载时实例化。

懒汉式

线程不安全

//饿汉式(静态代码块)
class Singleton{
    
    
    //1. 在该类内部创建实例对象
    private static Singleton instance;
    //2. 构造器私有化,使外部不能通过new创建对象
    private Singleton(){
    
    }
    //3. 对外提供唯一一个公共静态方法,来获得该类对象。当使用到该方法时,才创建对象。
    public static Singleton getInstance(){
    
    
        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}

优缺点:

  1. 能起到懒加载效果(使用该实例时才会创建),但只能在单线程使用。
  2. 在多线程中,如果一个线程已经进入到if里,但还没有执行new,而另一个线程刚好在进行if判断,此时就可能导致第一个线程成功new好,而第二个线程也进入if,然后new第二个实例。这样就破坏了单例模式。
  3. 结论:实际开发中,不要使用这种方式。

同步方法

//懒汉式(同步方法)
class Singleton{
    
    
    //1. 在该类内部创建实例对象
    private static Singleton instance;
    //2. 构造器私有化,使外部不能通过new创建对象
    private Singleton(){
    
    }
    //3. 对外提供唯一一个公共静态方法,来获得该类对象
    //改为同步方法 synchronized
    public static synchronized Singleton getInstance(){
    
    
        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}

优缺点:

  1. 解决了线程不安全问题。
  2. 效率太低。每个线程在想获得类实例的时,由于有synchronized,都要进行同步(等待使用该方法的进程用完)。然而这个方法只需要在实例化时同步,之后都只是返回实例,无需用到同步。
  3. 结论:实际开发中,不推荐使用。

同步代码块

//懒汉式(同步方法)
class Singleton{
    
    
    //1. 在该类内部创建实例对象
    private static Singleton instance;
    //2. 构造器私有化,使外部不能通过new创建对象
    private Singleton(){
    
    }
    //3. 对外提供唯一一个公共静态方法,来获得该类对象
    //改为同步代码块
    public static synchronized Singleton getInstance(){
    
    
        if (instance == null){
    
    
            synchronized(Singleton.class){
    
    
                instance = new Singleton();
            }
        }
        return instance;
    }
}

这种方式连线程安全都没解决,因为第一个线程正在new时,第二个线程对于if判断依然能通过,然后仍然会new。

双重检查

//双重检查
class Singleton{
    
    
    //1. 在该类内部创建实例对象
    private static volatile Singleton instance;
    //2. 构造器私有化,使外部不能通过new创建对象
    private Singleton(){
    
    }
    //3. 对外提供唯一一个公共静态方法,来获得该类对象
    //加入双重检查代码,解决线程安全和懒加载问题,同时保证了效率
    public static Singleton getInstance(){
    
    
        if (instance == null){
    
    
            synchronized (Singleton.class){
    
    
                if (instance == null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

 
volatile关键字: 线程对volatile变量的修改会立刻被其他线程所感知,即不会出现数据脏读的现象,从而保证数据的“可见性” 。

优缺点:

  1. Double-Check在多线程开发经常用到,我们在代码中进行了两次if判断,假如有三个线程A、B、C,当A和B都通过第一个if时,A先进入同步代码块实例化后就退出,B再进入同步代码块后instance不为null,就保证了线程安全。而后续的C在第一个if判断就无法通过,因此解决了效率问题。
  2. 实现了线程安全、懒加载、效率较高。
  3. 结论:在实际开发中,推荐使用这种单例设计模式。

静态内部类

//静态内部类
class Singleton{
    
    
    //1. 在该类内部创建实例对象
    private static volatile Singleton instance;
    //2. 构造器私有化,使外部不能通过new创建对象
    private Singleton(){
    
    }

    //3. 编写静态内部类
    private static class SingletonInstance{
    
    
        private static final Singleton INSTANCE = new Singleton();
    }

    //4. 对外提供唯一一个公共静态方法,来获得该类对象
    public static Singleton getInstance(){
    
    
        return SingletonInstance.INSTANCE;
    }
}

优缺点:

  1. 静态内部类的一个特性就是不会随着外部类的加载而加载,即Singleton加载时,SingletonInstance并不会加载,只有执行到Singleton.INSTANCE时才会加载,由此实现了懒加载效果。
  2. 类的静态属性只会在第一次加载类时得到初始化,所以这里JVM帮我们保证了线程安全,因为在类进行初始化时,其他线程无法进入。
  3. 实现了线程安全、懒加载,效率高。
  4. 结论:推荐使用。

枚举

package design_partten.singleton.type8;

public class SingletonTest08 {
    
    
    public static void main(String[] args) {
    
    
        Singleton singleton1 = Singleton.INSTANCE;
        Singleton singleton2 = Singleton.INSTANCE;
        System.out.println(singleton1 == singleton2);//结果为true,两个对象相同
        singleton1.sayOk();
    }
}

//静态内部类
enum Singleton{
    
    
    INSTANCE;
    public void sayOk(){
    
    
        System.out.println("ok~");
    }
}

优缺点:

  1. 借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建对象。
  2. 结论:推荐使用。

JDK源码对单例模式的使用

Runtime就是用的饿汉式单例模式,因为能保证我们必定会用,所以不会有内存浪费,且还能保证线程安全。

1615209917646

小结

  • 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建、销毁的对象,使用单例模式可以提高系统性能。
  • 当想实例化一个对象时,必须要记住使用相应的获取对象的方法,而不是使用new。
  • 单例模式使用场景:需要频繁创建、销毁的对象;创建对象耗时过多或耗费资源过多,但又需要经常用到的对象;工具类对象;频繁房访问数据库或文件的对象(例如数据源、SessionFactory)。

猜你喜欢

转载自blog.csdn.net/qq_39763246/article/details/114553165