【单例深思】Java 单例模式全解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Insert_day/article/details/69569382
什么是单例模式?

单例模式 是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例,并提供一个访问它的全局访问点。

单例模式的用途?

用于创建只需要一个实例的场景。

怎么实现单例模式?

单例模式的实现中有几个共同特点:

  1. 使用 private 声明类构造函数,这样就不能通过new 声明这个类的对象了,防止形成多个实例。
  2. 使用 private 声明类的单实例,防止外部直接访问。
  3. 提供唯一的全局访问方法,如getSignleton()来访问单实例。

单例模式性能的评价标准:
  1. 延迟加载
  2. 线程安全
  3. 并发访问性能
  4. 内存可见性
  5. 使用通用性
  6. 序列化不破坏单实例


Java中实现单例模式有多种方法,下面我们一一介绍:

  1. 饿汉式

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {}
    public static Singleton getSignleton(){
        return singleton;
    }
}  

顾名思义,这个实现就像是饥不择食的饿汉,在类装载时就忍不住实例化了。

非延迟加载:这种方式单实例  singleton  在类装载时就实例化,由于在静态成员singleton 使用了new 会在类加载时就初始化,这时候初始化 singleton 没有延迟加载的效果,如果这个单例类没有被用到,那么就会造成资源浪费。
线程安全类装载时实例化,一个类只会有同一类加载器加载一次,由JVM保证线程安全。


    2. 懒汉式

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

为了解决饿汉式的延迟加载问题,可以使用懒汉式实现,只有在需要使用的时候才实例化,能拖则托。
但是不能保证线程安全。

延迟加载:这种方式单实例  singleton  第一次被引用的时候初始化。
非线程安全:在多线程情况下,如果 singleton 还没有被创建,此时有可能多个线程同时进入 getInstance() 方法中,这时if 判断时 singleton 在都为空,则可能会创建多个实例,与单实例的要求不符,因此不是线程安全的。

    3.懒汉式改进版

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

为了使得懒汉式变成线程安全的,可以使用内置锁来保证 线程安全。
但是这样却引入了并发性能问题。

延迟加载:这种方式单实例  singleton  第一次被引用的时候创建。
线程安全 使用 synchronized  给  getInstance() 加锁,这样一次只能有一个线程进入 getInstance() 方法中,也就能满足线程安全了。
并发访问性能: 单实例只会创建一次,所以 getInstance() 中的 if  判断只有第一次请求的时候为true,非第一次请求都会直接返回单实例。 synchronized  给  getInstance() 整个方法加锁 ,方法内 所有的操作都会同步进行的,但是除了第一次创建单实例情况外,根本不需要同步操作,因此如果这个单实例访问频繁,会因为使用同步而影响使用性能。

     4.双重检测锁
    
     public   class  Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
 }

为了解决  懒汉式改进版  存在的性能问题,我们使用同步代码块缩小锁的范围,以提高并发量。
两次检测 singleton 是否为空,并使用锁同步实例化代码,因此得名双重检测锁。
但是,这种写法还是 可能 会出现可见性问题。

延迟加载:这种方式单实例  singleton  第一次被引用的时候创建。
线程安全 使用 synchronized  给创建单实例的代码枷锁,形成同步代码块,保证只创建一次
并发访问性能 只有第一次请求单实例时会进入同步代码块创建单实例,除此之外的其他请求不会进入到同步代码块中, getInstance() 在除第一次访问外相当于没有加锁,也不会造成性能问题。
内存可见性 JVM内存模型有两个重要的概念,叫做指令重排序和内存可见性 getSingleton() 方法可能会出现这种情况,有多个线程进入了该方法,有一个线程获得了锁进行单实例创建,其他线程直接返回,但是返回的对象却未正常实例化,造成使用时出错。

    5.双重检测锁改进版

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

为了解决 双重检测锁中出现的问题,需要使用  volatile   声明 singleton ,以保证单实例创建成功。
但是,这种方法不是通用的。

延迟加载:这种方式单实例  singleton  第一次被引用的时候创建。
线程安全 使用 synchronized  给创建单实例的代码枷锁,形成同步代码块,保证只创建一次
并发访问性能 只有第一次请求单实例时会进入同步代码块创建单实例,除此之外的其他请求不会进入到同步代码块中, getInstance() 在除第一次访问外相当于没有加锁,也不会造成性能问题。
可见性: 使用 volatile   声明 singleton volatile 可以保证内存可见性并告诉编译器不进行指令重排序,当 有多个线程进入了 getSingleton() 方法,有一个线程获得了锁进行单实例创建,其他线程直接返回,这时只要单实例完成创建,其他线程就可以立即获得该实例,并且编译器不会进行指令重排,程序会按我们写的代码顺序执行。
通用性 volatile  Java 1.5中才发挥上述作用,因此该方法只能在 Java 1.5及 以上才可使用。

6.静态内部类式

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

延迟加载 SingletonHolder 为静态内部类, Singleton  初始化时不会初始化 SingletonHolder ,只有 通过 显示调用 getInstance() 方法时,才会初始化 SingletonHolder 类,从而实例化单实例,满足延迟加载
线程安全:单实例在类加载过程中创建,JVM 保证线程安全
并发访问性能 没有加锁,不会造成并发性能问题。
可见性: 安全,没有先判断再创建的模式
通用性 通用,没有使用 volatile

    7.枚举实现
    
     public   enum  Singleton {
    INSTANCE;
    private Singleton() {}
  }

实际工作使用很少。

延迟加载 满足延迟加载
线程安全创建枚举默认就是线程安全的。
并发访问性能 没有加锁,不会造成并发性能问题。
可见性: 安全,没有先判断再创建的模式
通用性1.5之前不可用
序列化问题: 无。

    8. 序列化安全

     public   class  Singleton {
    private static volatile Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    private Object readResolve() {
        return singleton;
    }
}

一旦单例实现了序列化接口,那么它可能就不是序列化安全的了,存了枚举实现外,其他方式都存在序列化问题,只要在 Singleton  类中定义 readResolve () 就可以解决该问题。

总结
 
实现单例的方法很多,个人推荐静态内部类的方式,功能完备,使用简单,易于理解。




猜你喜欢

转载自blog.csdn.net/Insert_day/article/details/69569382
今日推荐