参考
单例模式(Singleton Pattern)
确保一个类有且仅有一个实例,并且为客户提供一个全局访问点。
特点
1) 保证被访问资源对象在内存中只有一个实例,节约了系统内存资源,也避免了对资源多重占用;
2) 封装了访问实例方法,提供全局访问点,严格控制客户的访问方式;
3) 通常常驻内存,不会频繁创建/销毁,节约了系统开销;
缺点
1) 没有抽象层,难以扩展;
2) 类的职责过重,往往一个单例负责所有与之相关功能,违背了类设计的“职责单一”原则;
3) 单例如果持有context,容易造成内存泄漏;
4) 全局共享一个实例资源,难以隔离问题,进行单独测试;
适用场景
1)全局随时可能需要访问,访问方式复杂,而且资源受限;
2)需要常驻内存,避免频繁创建、销毁的资源;
demo
单例模式通用UML类图
单例模式的8种写法与多线程
单例模式为了不让外部随意构建实例,一般需要将构造函数声明为private,在获取实例对象时,就无法通过动态函数来读取(因为此时实例还未初始化),故只能通过类方法(static方法)来获取实例引用。
1. 饿汉式 静态常量 立即加载
// Singleton.java // 饿汉式 静态常量 public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } public void displaySingleton(){ System.out.println("单例初始化方式: 饿汉式 静态常量"); } }
特点:简单,类装载时完成初始化,不存在多线程同步问题;
优点:没有延迟实例化,如果程序一直没有使用,会造成资源浪费;
2.饿汉式 静态代码块 立即加载
与饿汉式 静态常量类似,只是把实例初始化放在了类的静态代码块中,而非放在实例引用定义处。
// Singleton1.java // 饿汉式 静态代码块 public class Singleton1 { private static Singleton1 instance; static{ instance = new Singleton1(); } private Singleton1(){} public static Singleton1 getInstance(){ return instance; } public void displaySingleton(){ System.out.println("单例初始化方式: 饿汉式 静态代码块"); } }
3. 懒汉式 延迟加载 线程不安全
如果在执行多个线程同时执行到instance == null,就会造成多次实例化。只适合单线程情况使用。
// Singleton2.java // 懒汉式 延迟实例化 线程不安全 public class Singleton2 { private static Singleton2 instance; private Singleton2(){} public static Singleton2 getInstance(){ if(instance == null){ instance = new Singleton2(); } return instance; } }
4.懒汉式 延迟加载 线程安全(同步方法)
效率低下,每次线程通过getInstance获取实例,甚至在对象已经实例化后,都要先等待别的线程释放资源。
// Singleton3.java // 懒汉式 延迟实例化 线程安全(同步方法) public class Singleton3 { private static Singleton3 instance; private Singleton3(){} public static synchronized Singleton3 getInstance(){ if(instance == null){ instance = new Singleton3(); } return instance; } public void displaySingleton(){ System.out.println("Singleton3单例初始化方式: 懒汉式 延迟实例化 线程安全(同步方法)"); } }
5. 懒汉式 延迟实例化 线程安全(同步代码块)
效率比4(同步方法)高,但是多线程可能会出现多次实例化的问题。
//Singleton4.java //懒汉式 延迟实例化 线程安全(同步代码块) public class Singleton4 { private static Singleton4 instance; private Singleton4(){} public static Singleton4 getInstance(){ if(instance == null) { synchronized (Singleton4.class) { instance = new Singleton4(); } } return instance; } public void displaySingleton(){ System.out.println("Singleton4单例初始化方式: 懒汉式 延迟实例化 线程安全(同步代码块)"); } }
6. 懒汉式 线程安全(双重检查)
综合了4,5即线程安全(同步方法)和线程安全(同步代码块)的优缺点,解决了4的低效问题,又解决了5的多次实例化不安全问题。
// Singleton5.java // 懒汉式 线程安全(双重检查) public class Singleton5 { private static Singleton5 instance; private Singleton5(){} public static Singleton5 getInstance(){ if(instance == null){ synchronized(Singleton5.class){ if(instance == null) { instance = new Singleton5(); } } } return instance; } public void displaySingleton(){ System.out.println("Singleton5单例初始化方式: 懒汉式 线程安全(双重检查)"); } }
7. 静态内部类
与饿汉式类似,都是通过类的装载机制来初始化实例,不过,既解决了饿汉式无法延迟实例化的问题,又解决了线程安全的问题。
// Singleton6.java // 静态内部类 public class Singleton6 { private Singleton6(){} private static class SingletonInstance{ private static final Singleton6 INSTANCE = new Singleton6(); } public static Singleton6 getInstance(){ return SingletonInstance.INSTANCE; } public void displaySingleton(){ System.out.println("Singleton6单例初始化方式: 静态内部类 线程安全"); } }
8. 枚举类型
通过枚举类型在构造的时候,被实例化。不仅能解决多线程问题,还能防止反序列化创建新的对象。JDK1.5之后才加入,现使用较少。
// Singleton7.java // 枚举类型 public enum Singleton7 { INSTANCE; public void display(){ System.out.println("Singleton7单例初始化方式: 枚举类型"); } }
总结
1. 实现单例模式的核心在与私有化构造方法,在getInstance方法中读取实例引用。
2. 如果是类加载时,就实例化,就成为饿汉式;否则,在getInstance方法中才实例化称为懒汉式。
3. 各种实现方法比较
实现方法 | 特点 | 是否线程安全 | 是否推荐 |
饿汉式,静态常量 | 立即加载 | 是 | 可以用 |
饿汉式,静态代码块 | 立即加载 | 是 | 可以用 |
一般懒汉式 | 延迟加载 | 否 | 多线程不可用 |
懒汉式,同步方法 | 延迟加载,效率低 | 是 | 可以用,不推荐 |
懒汉式,同步代码块 | 延迟加载,多次实例化 | 否 | 不可用 |
懒汉式,双重验证 | 延迟加载,效率高 | 是 | 推荐 |
静态内部类 | 延迟加载,效率高 | 是 | 推荐 |
枚举类型 | 延迟加载,效率高,应用较少(>JDK1.5) | 是 | 推荐 |