设计模式-单例模式
时间:2023/01/04
一. 单例模式介绍
作用:所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
实现方式(八种):
1. 饿汉式(静态常量)
2. 饿汉式(静态代码块)
3. 懒汉式(线程不安全)
4. 懒汉式(线程安全,同步方法)
5. 懒汉式(线程不安全,同步代码块)
6. 双重检查
7. 静态内部类
8. 枚举
二. 饿汉式(静态常量)
步骤:
1. 构造器私有化
2. 类的内部创建对象
3. 向外暴露一个静态的公共方法
代码:
package singleton; public class SingletonTest01 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // true } } class Singleton{ // 1. 构造器私有化,外部不能new private Singleton(){ } // 2. 本类内部创建对象实例 private final static Singleton instance = new Singleton(); // 3. 提供一个公有的静态方法,返回对象实例 public static Singleton getInstance(){ return instance; } }
优缺点说明:
1. 优点:这种写法比较简单,就是在类加载的时候就完成了实例化。避免了线程同步问题。
2. 缺点:在类加载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
3. 这种基于类加载的机制避免了多线程同步问题。但是在类加载时就进行实例化会存在很多问题,在单例模式中是通过调用getInstance方法来实现类加载,这种导致类加载的方式是我们希望看到的,因为我们调用getInstance方法就说明我们会用到这个单例对象,但是在实际中导致类加载的原因会有很多,如果不是通过调用getInstance方法导致类加载,就会出现我们不想使用单例对象,但是该对象却已经存在内存的情况,这会浪费内存空间,没有达到Lazy Loading的效果。
4. 结论:这种单例模式可用,但是可能造成内存浪费。
三. 饿汉式(静态代码块)
步骤:
1. 构造器私有化
2. 声明一个该类的对象
3. 在静态代码块中创建单例对象
4. 提供一个公有的静态方法,返回对象实例
package singleton; public class SingletonTest02 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // true } } class Singleton02{ // 1. 构造器私有化,外部不能new private Singleton02(){ } // 2. 声明一个该类的对象 private static Singleton02 instance; // 3. 在静态代码块中创建单例对象 static { instance = new Singleton02(); } // 4. 提供一个公有的静态方法,返回对象实例 public static Singleton02 getInstance(){ return instance; } }
优缺点说明:
1. 与静态常量方法类似,优缺点和上面一样。
2. 结论: 这种单例模式可用,但是可能造成内存浪费。
四. 懒汉式(线程不安全)
步骤:
1. 声明一个该类的静态私有对象
2. 构造器私有化
3. 提供一个静态的公有方法,当使用到该方法时,采取创建该类的对象
package singleton.type3; public class SingletonTest03 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // true } } class Singleton{ private static Singleton instance; private Singleton(){} // 提供一个静态的公有方法,当使用到该方法时,才去创建instance(懒汉式) public static Singleton getInstance(){ if(instance == null) instance = new Singleton(); return instance; } }
优缺点说明:
1. 起到了Lazy Loading的效果,但是只能在单线程下使用。
2. 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式(因为会违反单例模式的初衷)。
3. 结论:在实际开发中,不要使用这种方式。
五. 懒汉式(线程安全,同步方法)
步骤:
1. 声明一个该类的静态私有对象
2. 构造器私有化
3. 提供一个静态的公有方法,加入同步处理代码,解决线程安全问题
package singleton.type4; public class SingletonTest04 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // true } } class Singleton{ private static Singleton instance; private Singleton(){} // 提供一个静态的公有方法,加入同步处理的代码(synchronized),解决线程安全问题 public static synchronized Singleton getInstance(){ if(instance == null) instance = new Singleton(); return instance; } }
优缺点说明:
1. 解决了线程不安全问题。
2. 效率太低了,每个线程在想获取类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。(每次调用getInstance()方法都需要同步,实际上真正实例化的只有一次)
3. 结论:在实际开发中,不推荐使用这种方式。
六. 懒汉式(线程不安全,同步代码块)
1. 声明一个该类的静态私有对象
2. 构造器私有化
3. 提供一个静态的公有方法,加入同步处理块,但是无法解决线程安全问题
package singleton.type5; public class SingletonTest05 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // true } } class Singleton{ private static Singleton instance; private Singleton(){} // 提供一个静态的公有方法 public static Singleton getInstance(){ // 同步代码块 if(instance == null){ synchronized (Singleton.class){ instance = new Singleton(); } } return instance; } }
优缺点说明:
1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方式效率太低,改为同步产生实例化的代码块。
2. 但是这种同步并不能起到线程同步的作用。加入一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
3. 结论:在实际开发中,不能使用这种方式。
七. 双重检查
volatile关键字:一种轻量级的同步机制,保证可见性,不保证原子性,并且禁止指令重排。
具体可见参考:https://blog.csdn.net/u012723673/article/details/80682208
代码如下:
package singleton.type6; public class SingletonTest06 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // true } } class Singleton{ // volatile: 一种轻量级的同步机制,保证可见性,不保证原子性,并且禁止指令重排。 private static volatile Singleton instance; private Singleton(){} // 提供一个公有静态方法 public static Singleton getInstance(){ // 第一次检查 if(instance == null){ // 同步代码块 synchronized (Singleton.class){ // 第二次检查 if(instance == null){ instance = new Singleton(); } } } return instance; } }
双重检查的思想:假设有三个线程a、b、c,如果线程a和b已经通过了第一次检查,但是都还没有进入同步代码块,而线程c还没有进入第一次检查,此时如果线程a先进入同步代码块,则线程b不能进入,然后a通过第二次检查并创建对象,之后a会退出同步代码块,此时b就可以进入到同步代码块中,但是由于instance对象已经被创建,b就不会创建新的对象,保证了内存中只有一个对象,然后b退出同步代码块。对于线程c,它此时执行第一次检查,由于instance不为null,所以不会执行到同步代码块,这样会大大提高效率,解决了懒汉式(同步方法)存在的问题。
优缺点说明:
1. Double-Check(双重检查)概念是多线程开发中常使用到的,如上述代码中所示,我们进行了两次if(singleton == null)检查,这样就可以保证线程安全。
2. 这样,实例化代码只用执行一次,后面再次访问时,判断if(singleton == null), 直接return实例化对象,也避免了反复进行方法同步。
3. 线程安全,延迟加载,效率较高。
4. 结论:在实际开发中,推荐使用这种单例设计模式。
八. 静态内部类
代码如下:
package singleton.type7; public class SingletonTest07 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // true } } class Singleton{ // 1. 构造器私有化 private Singleton(){} // 2. 写一个静态内部类,该类中有一个静态属性instance private static class SingletonInstance{ private static final Singleton instnace = new Singleton(); } // 3. 提供一个静态公有方法 public static Singleton getInstance(){ return SingletonInstance.instnace; } }
静态内部类方法利用了静态内部类的两个特点:
1. 当外部类被加载(装载)时,静态内部类不会被加载,这实现了Lazy Loading。
2. 当调用到了getInstance()方法时,静态内部类会被加载,该类只会被加载一次,而且类加载是线程安全的,所以不会有安全问题。
优缺点说明:
1. 这种方式采用了类加载的机制来保证初始化实例时只有一个线程。
2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装在SingletonInstance类,从而完成instance的实例化。
3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的的。
4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
5. 结论:推荐使用。
九. 枚举
代码如下:
package singleton.type8; public class SingletonTest08 { public static void main(String[] args) { Singleton instance = Singleton.instance; Singleton instance1 = Singleton.instance; System.out.println(instance == instance1); // true instance.sayOK(); // ok~ } } enum Singleton{ instance; // 属性 public void sayOK(){ System.out.println("ok~"); } }
优缺点说明:
1. 借助JDK 1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
2. 这种方式是Effective Java作者Josh Bloch提倡的方式。
3. 结论:推荐使用。
十. 单例模式注意事项和细节说明
注意事项和细节说明:
1. 单例模式保证了系统内存中只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。
3. 单例模式使用的场景:需要频繁的进行创建和销毁对象、创建对象耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。