【设计模式学习笔记】单例模式
单例设计模式介绍
单例设计模式,就是采取一定的方法保证整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个其对象的实例方法。
单例模式八种实现方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
- 步骤如下
- 构造器私有化(防止用户new对象)
- 在类的内部创建对象
- 向外暴露一个静态的公共方法getInstance
public class Solution { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); Singleton singleton1 = Singleton.getInstance(); System.out.println(singleton == singleton1); // true System.out.println(singleton); // Singleton@1b6d3586 System.out.println(singleton1); // Singleton@1b6d3586 } } class Singleton { // 私有化构造器 private Singleton() {} // 创建对象 private static final Singleton instance = new Singleton(); // 静态公共方法 public static Singleton getInstance() { return instance; } }
- 优点:在类装载的时候就完成实例化,避免了线程同步的问题。
- 缺点:在类装载的时候就完成实例化对象,没有懒加载的效果。如果一直没有使用过这个对象,就会造成内存的浪费。
饿汉式(静态代码块)
public class Solution { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); Singleton singleton1 = Singleton.getInstance(); System.out.println(singleton == singleton1); // true System.out.println(singleton); // Singleton@1b6d3586 System.out.println(singleton1); // Singleton@1b6d3586 } } class Singleton { private Singleton() {} private static final Singleton instance; static { instance = new Singleton(); } public static Singleton getInstance() { return instance; } }
懒汉式(线程不安全)
class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
- 优点:起到了懒加载的效果,但是只能在单线程下使用。
- 缺点:如果在多线程的环境下,当两个线程同时进入instance == null,那么此时就有可能创建两个不同的对象。
懒汉式(线程安全,同步方法)
class Singleton { private static Singleton instance; private Singleton() {} public synchronized static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
- 优点:解决线程安全问题
- 缺点:效率低,多线程情况下,每一个线程在获得实例的时候在调用方法的时候都要进行同步。实际上这个方法只执行一次实例化的方法即可,后面如果想要再次获得对象只需要直接返回即可。但是上面这种方式,在线程没有拿到锁的时候都要进行等待。
懒汉式(线程安全,同步代码块)
class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { instance = new Singleton(); } } return instance; } }
- 这种方式会引起线程安全问题,在多线程并发的情况下,可能创建两个不同的实例。
双重检查
class Singleton { // volatile 保证了可见性和有序性,可以让修改值立即更新到主存 private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 在堆内存分配对象的内存空间 // 在堆内存上初始化对象 // 设置 instance 指向刚分配的内存地址 // 上面两步可能发生指令重排序,可能造成 instance 无法指向正常的对象 // 所以需要加入 volatile 关键字 } } } return instance; } }
- 这种方式在多线程开发中经常用到,进行了两次检查,可以解决线程安全问题。
- 上述代码,实例化代码只执行一次,避免反复的进行方法同步,线程安全,延迟加载,效率较高。
- 在实际开发的过程中,比较推荐这种单例设计模式。
静态内部类
class Singleton { // 当 Singleton 进行类加载的时候,静态内部类不会被加载 private Singleton() {} public static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; // 使用 SingletonInstance 会被装载,只会装载一次 } }
- 这种方式采用类装载机制来保证初始化实例的时候只有一个线程。
- 静态内部类在Singleton装载的时候,静态内部类不会被加载,在需要调用getInstance方法的时候才会被加载。因为在类加载的时候其中的静态属性只会被加载一次,所以这里JVM可以保证线程安全性,在类进行初始化的时候其他线程是无法进入的。
- 避免了线程不安全,利用静态内部类的特点实现了延迟加载,效率比较高。
枚举
public class SingletonTest { public static void main(String[] args) { Singleton singleton = Singleton.INSTANCE; Singleton singleton1 = Singleton.INSTANCE; System.out.println(singleton == singleton1); // true System.out.println(singleton.hashCode()); // 460141958 System.out.println(singleton1.hashCode()); // 460141958 } } // 枚举,使用枚举可以实现单例 enum Singleton { INSTANCE; }
- 借助JDK 1.5中的枚举来实现单例模式,能够避免线程同步问题,而且能够防止反序列化重新创建新的对象。
单例模式注意事项和细节说明
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统的资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提升系统性能。
- 当想要实例化一个单例类的时候,需要记住相应的获取对象的方法。
- 使用场景:需要频繁创建和销毁对象、创建对象耗时过多的时候或者耗费资源过多(重量级对象)的时候,但是又经常使用的对象、工具类对象、频繁访问数据库的对象或者文件的对象。例如:数据源、session工厂等。