Java设计模式优化-单例模式

单例模式概述

单例模式是一种对象建立模式,用于产生一个类的具体事例。使用单例模式能够确保整个系统中单例类只产生一个实例。有下面两大好处:java

  1. 对于频繁建立的对象,节省初第一次实例化以后的建立时间。
  2. 因为new操做的减小,会下降系统内存的使用频率。减轻GC压力,从而缩短GC停顿时间

建立方式:安全

  1. 单例做为类的私有private属性
  2. 单例类拥有私有private构造函数
  3. 提供获取实例的public方法

单例模式的角色:性能优化

角色 做用
单例类 提供单例的工厂,返回类的单例实例
使用者 获取并使用单例类

类基本结构:
单例模式类图多线程

单例模式的实现

1.饿汉式

public class HungerSingleton {
    //1.饿汉式
    //私有构造器
    private HungerSingleton() {
        System.out.println("create HungerSingleton");
    }
    //私有单例属性
    private static HungerSingleton instance = new HungerSingleton();
    //获取单例的方法
    public static HungerSingleton getInstance() {
        return instance;
    }
}

注意:jvm

  1. 单例修饰符为static JVM加载单例类加载时,直接初始化单例。没法延时加载。若是此单例一直未被使用,单Singleton 由于调用静态方法被初始化则会形成内存的浪费。
  2. getInstance()使用static修饰,不用实例化能够直接使用Singleton.getInstance()获取单例。
  3. 因为单例由JVM加载类的时候建立,因此不存在线程安全问题。

2.简单懒汉式

public class Singleton {
    //2.1简单懒汉式(线程不安全)
    //私有构造器
    private Singleton() {
        System.out.println("create Singleton");
    }
    //私有单例属性[初始化为null]
    private static Singleton instance = null;
    //获取单例的方法
    public static Singleton getInstance() {
        if(instance == null) {
            //此处instance实例化
            //首次调用单例时会进入  达成延时加载
            instance = new Singleton();
        }
        return instance;
    }
}
  • 因为未使用 synchronized 关键字,因此当线程1调用单例工厂方法Singleton.getInstance() 且 instance 未初始化完成时,线程2调用此方法会将instance判断为null,也会将instance从新实例化赋值,此时则产生了多个实例!
  • 如需线程安全能够直接给getInstance方法上加synchronized关键字,以下:
public class Singleton {
    //2.2简单懒汉式(线程安全)
    //私有构造器
    private Singleton() {
        System.out.println("create Singleton");
    }
    //私有单例属性[初始化为null]
    private static Singleton instance = null;
    //获取单例的方法 将此方法使用synchronized关键字同步
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            //此处instance实例化
            //首次调用单例时会进入  达成延时加载
            instance = new Singleton();
        }
        return instance;
    }
}

面临的问题:函数

  • 因为对getInstance()整个方法加锁,在多线程的环境中性能比较差。

3.DCL 懒汉式(双重检测)

简单懒汉式(线程安全)中,对getInstance()方法加锁,致使多线程中性能较差,那么是否能够 减少锁的范围,使不用每次调用geInstance()方法时候都会去竞争锁?

DCL(Double Check Locking)双重检测 就是这样一种实现方式。性能

public class DCLLazySingleton {
    //3.DCL
    //私有构造器
    private DCLLazySingleton() {
        System.out.println("create DCLLazySingleton");
    }
    //私有单例属性[初始化为null] volatile 保证内存可见性 防止指令重排
    private static volatile DCLLazySingleton instance = null;//step1
    //获取单例的方法
    public static DCLLazySingleton getInstance() {
        //这里判null 是为了在instance有值时,不进入加锁的代码块,提升代码性能。
        if(instance == null) {
            //缩小锁范围 因为是静态方法方法调用的时候不依赖于实例化的对象 加锁只能使用类
            synchronized (DCLLazySingleton.class) {
                //这里判null 是为了配合volatile解决多线程安全问题
                if(instance == null) {
                    instance = new DCLLazySingleton();
                }
            }
        }
        return instance;
    }
}

注意:优化

  1. 传统DCL(step1处并未使用 volatile 或使用了但在JDK1.8以前)面临的问题:spa

    • 因为初始化单例对象new DCLLazySingleton() 操做并非原子操做,因为这是不少条指令,jvm可能会乱序执行。
    • 在线程1初始化对象可能并未完成,可是此时已经instance对象已经不为null。(已经分配了内存,可是构造方法还未执行完【可能有一些属性的赋值未执行】)
    • 此时线程2再获取instance 则不为null 直接返回。那么此时线程2获取的则为‘构造方法未执行完的instance对象’。则不能保证线程安全。
  2. 解决方式:.net

    • 加上volatile关键字,volatile保证内存可见性,内存屏障,防止指令排!
    • 加上volatile关键字后,线程2获取的构造方法未执行完的instance对象,会在线程1修改以后同步到线程2(volatile 内存空间)。因此解决了线程安全问题
  3. 参考:

4.懒汉式(静态内部类)

public class StaticSingleton {
    //私有构造器
    private StaticSingleton() {
        System.out.println("create StaticSingleton!");
    }
    //获取单例的方法
    public static StaticSingleton getInstance() {
        return SingletonHolder.instance;
    }
    //静态内部类 持有单例 做为静态属性。
    //因为只有在访问属性时才会加载静态类初始化instance。因此实现了懒加载。且因为JVM保证了类的加载为线程安全,因此为线程安全的。
    private static class SingletonHolder {
        //私有单例属性
        private static StaticSingleton instance = new StaticSingleton();
    }
}

注意:

  1. 因为StaticSingleton类被加载时,内部的私有静态类SingletonHolder并不会被加载,因此并不会初始化单例instance,当getInstance()被调用时SingletonHolder.instance 才会加载SingletonHolder,因为JVM保证了类的加载为线程安全,所以线程安全。
  2. 此方式既能够作到延时加载,也不会由于同步关键字影响性能。是一种比较完善的实现。推荐使用

5.枚举单例

public enum EnumSingleton {
    INSTANCE();
    EnumSingleton() {
        System.out.println("create EnumSingleton");
    }
}
  • 线程安全,且可以抵御反射与序列化。
  • 推荐使用

例外状况

上述的单例实现方式仍是会面临一些特殊状况不能保证惟一实例:

  1. 反射调用私有构造方法。
  2. 序列化后反序列化会生成多个对象。能够实现私有readResolve方法。readObject()如同虚设,直接使用readResolve替换本来返回值。以下:
private Object readResolve () {
    //返回当前对象
    return instance;
    }

因为上述两状况比较特殊,因此没有特别关注。

参考书籍


《Java程序性能优化》 -葛一鸣 等编著

相关文章
相关标签/搜索