JAVA设计模式之单例模式详细分析(全)

目录

  • 前言
  • 1. 定义
  • 2. 实现
    • 2.1 懒汉式(线程不安全)
    • 2.2 饿汉式(线程安全)
    • 2.3 懒汉式(线程安全)
    • 2.4 双重检查锁实现(线程安全)
    • 2.5 静态内部类实现(线程安全)
    • 2.6 枚举类实现(线程安全)
  • 3. 总结

前言

主要讲解单例模式的几种写法,以及每种写法的区别优劣势
这一模式的目的是使得类的一个对象成为系统中的唯一实例

1. 定义

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

单例模式的例子

public class Singleton {
    private Singleton(){
    }
    private static volatile Singleton instance = null;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

所谓单例模式的三要素:
一是某个类只能有一个实例
二是它必须自行创建这个实例
三是它必须自行向整个系统提供这个实例

2. 实现

一共有6种实现方法

2.1 懒汉式(线程不安全)

先不创建实例,当被调用的时候,再创建实例

public class Singleton {
     private static Singleton uniqueInstance;

     private Singleton() {

    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

优点:延迟实例化,没被用到的话节约了系统资源。

缺点: 线程不安全的,并发环境下很可能出现多个Singleton实例,未实例化就进行实例操作

2.2 饿汉式(线程安全)

不管用不用到,先实例化

public class Singleton {

    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }

}

优点: 提前实例,只有一个实例,避免线程不安全

缺点: 没用到的话,会造成资源浪费

2.3 懒汉式(线程安全)

在get方法上加了一把锁。如果多个线程访问,只有进入线程拿到锁,才可执行,避免线程不安全

public class Singleton {
    private static Singleton uniqueInstance;

    private static singleton() {
    }

    private static synchronized Singleton getUinqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }

}

优点: 延迟实例化,节约了资源,并且是线程安全的。

缺点: 虽然解决了线程安全问题,但是性能降低了。有了锁,进入该方法的会使线程阻塞,等待时间过长

2.4 双重检查锁实现(线程安全)

双重检查锁改进懒汉式(线程安全)
改进方法是:将锁的位置改变,并且多加一个检查。
先判断实例是否已经存在,在判断没有实例化的时候,多个线程进去了,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题

解决办法就是加一个 volatile 关键字修饰 uniqueInstance ,volatile 会禁止 JVM 的指令重排,就可以保证多线程环境下的安全运行

public static Singleton getInstance() {
        if (singleton == null) {  
            synchronized (Singleton.class) {  
               if (singleton == null) {  
                  singleton = new Singleton(); 
               }  
            }  
        }  
        return singleton; 
    }

优点: 延迟实例化,节约了资源;线程安全;

缺点: volatile 关键字,对性能也有一些影响。

2.5 静态内部类实现(线程安全)

外部类被加载时,静态内部类并没有被加载进内存

当调用 getInstance方法时,会运行LazyHolder.INSTANCE; 此时静态内部类LazyHolder 才会被加载进内存,并且初始化 INSTANCE 实例,而且 JVM 会确保 INSTANCE 只被实例化一次

优点: 延迟实例化,节约了资源;且线程安全;性能也提高了

public class Singleton {  
    private static class LazyHolder {  
       private static final Singleton INSTANCE = new Singleton();  
    }  
    
    private Singleton (){}  
    
    public static final Singleton getInstance() {  
       return LazyHolder.INSTANCE;  
    }  
}  

2.6 枚举类实现(线程安全)

默认枚举实例的创建就是线程安全的,且在任何情况下都是单例

public enum Singleton {

    INSTANCE;

    //添加自己需要的操作
    public void doSomeThing() {

    }

}

优点:防止反射和反序列化调用

关于序列化可看我之前的文章
java之序列化与反序列化的详细解析(全)

简单的来说:
序列化:java对象-》字节码序列。
反序列化:字节码序列-》java对象

要想防止多个对象,也就是一个实例,即反序列化只能有一次
也就是实例对象重写这个方法

private Object readResolve() throws ObjectStreamException{
        return singleton;
}

3. 总结

  1. 饿汉式,直接在方法外定义(线程安全,调用效率高,但是不能延时加载);
  2. 懒汉式,加了锁,方法内实例化(线程安全,调用效率不高,但是能延时加载);
  3. Double CheckLock实现单例:DCL也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用);
  4. 静态内部类实现模式(线程安全,调用效率高,可以延时加载);
  5. 枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)。

所谓的延时不延时加载,主要看加载这个类的时候会不会出来这个实例

而枚举类一加载,就会出来。饿汉式定义在方法外,不管用没用直接加载,所以不能延时加载

具体使用的场景总结:

  1. 频繁实例化又销毁的对象
  2. 经常使用的对象如数据库连接池,使用单例模式,可以提高性能,降低资源损坏。
  3. 使用线程池之类的控制资源时,使用单例模式,可以方便资源之间的通信。

你可能感兴趣的:(java,单例模式,java,开发语言)