创建型模式 - 单例设计模式

简介

  • 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类 只能存在一个对象实例,并且该类提供了一个取得其对象的方法

实现单例设计模式的十种方式

  • 饿汉式 线程安全的(静态成员)

  • 饿汉式 线程安全的(静态代码块)

  • 懒汉式 线程不安全的 (一次判断)

  • 懒汉式 线程不安全的 (两次判断)

  • 懒汉式 线程安全的 (方法加锁)

  • 懒汉式 线程安全的 (静态代码块加锁)

  • 懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁

  • 懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁+Volatile关键字

  • 静态内部类 线程安全的

  • 枚举 绝对安全的


饿汉式 线程安全的(静态成员)

步骤

  • 创建一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法

代码实现

  1. public class Case01 {
  2. private static Case01 instance = new Case01();
  3. private Case01() {
  4. }
  5. public static Case01 getInstance() {
  6. return instance;
  7. }
  8. }

优缺点比较

  • 优点

    • 这种写法比较简单,就是在类加载的时候就去完成实例化,避免了线程同步的问题
  • 缺点

    • 在类装载的时候就完成了实例化,没有达到懒加载的效果,如果从始至终都没有使用过这个实例,那么这个实例就浪费了内存
  • 这种方式基于 classloder机制避免了多线程的同步问题.不过,instance在类加载的时候就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他种方法(或者其他种静态方法)导致类装载,这个时候 就没有懒加载的效果
  • 结论:这种单例模式可用,但是 可能 造成内存浪费

饿汉式 线程安全的(静态代码块)

步骤

  • 声明一个私有对象
  • 静态代码块创建对象
  • 私有化构造器
  • 暴漏一个外界访问的方法

代码实现

  1. public class Case02 {
  2. private static Case02 instance;
  3. static {
  4. instance = new Case02();
  5. }
  6. private Case02() {
  7. }
  8. public static Case02 getInstance() {
  9. return instance;
  10. }
  11. }

优缺点比较

  • 种方式和上面的方式其实类似,只不过将类实例化放在静态代码块,但是需要注意的是,instance的声明必须在静态代码块之前,否则可能会 为 null 。
  • 其实也就是在类装载的时候,就执行静态代码块中的代码,初始化类的实例,优缺点和上面是一样的
  • 结论:这种单例模式可用,但是 可能 会造成 内存的浪费

懒汉式 线程不安全的 (一次判断)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

  1. public class Case03 {
  2. private static Case03 instance;
  3. private Case03() {
  4. try {
  5. Thread.sleep(100);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. public static Case03 getInstance() {
  11. if (instance == null) {
  12. instance = new Case03();
  13. }
  14. return instance;
  15. }
  16. }

优缺点

  • 优点,起到了懒加载的效果,什么时候用什么时候再加载,但是只能再单线程下使用后

  • 缺点:如果在多线程情况下,一个线程进入了if(instance == null)还没有来得及向下进行,而另外一个线程也执行了这个判断语句,这个时候会产生多个实例,所以在多线程情况下不可以使用这种方式

  • 总结:实际开发中不可以使用这种方式

懒汉式 线程不安全的 (两次判断)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

  1. public class Case04 {
  2. private static Case04 instance;
  3. private Case04() {
  4. try {
  5. Thread.sleep(100);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. public static Case04 getInstance() {
  11. if (instance == null) {
  12. if (instance == null) {
  13. instance = new Case04();
  14. }
  15. }
  16. return instance;
  17. }
  18. }

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,但是只能再单线程下使用后,

  • 缺点:如果在多线程情况下,一个线程进入了if(instance == null)还没有来得及向下进行,而另外一个线程也执行了这个判断语句,这个时候会产生多个实例,所以在多线程情况下不可以使用这种方式,虽然多判断了一次,但是还是会多线程并发访问的问题

  • 总结:实际开发中不可以使用这种方式

懒汉式 线程安全的 (方法加锁)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

  1. public class Case05 {
  2. private static Case05 instance;
  3. private Case05() {
  4. try {
  5. Thread.sleep(100);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. public synchronized static Case05 getInstance() {
  11. if (instance == null) {
  12. instance = new Case05();
  13. }
  14. return instance;
  15. }
  16. }

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 缺点:锁所用于方法,范围太大了,影响效率

  • 总结:实际开发中可以使用这种方式,但是又更好的方法

懒汉式 线程安全的 (静态代码块加锁)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

  1. public class Case06 {
  2. private static Case06 instance;
  3. private Case06() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. public static Case06 getInstance() {
  11. synchronized (Case06.class) {
  12. if (instance == null) {
  13. instance = new Case06();
  14. }
  15. return instance;
  16. }
  17. }
  18. }

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 缺点:锁所用于代码块。每次进入都要一次加锁判断,范围太大了,影响效率

  • 总结:实际开发中可以使用这种方式,但是又更好的方法

懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

  1. public class Case07 {
  2. private static Case07 instance;
  3. private Case07() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. public static Case07 getInstance() {
  11. if (instance == null) {
  12. synchronized (Case07.class) {
  13. if (instance == null) {
  14. instance = new Case07();
  15. }
  16. }
  17. }
  18. return instance;
  19. }
  20. }

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 缺点:锁所用于代码块。第一次进入都要一次加锁判断,之后进入不需要加锁,但是有可能会出问题

  • 总结:实际开发中可以使用这种方式,但是又更好的方法

懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁+Volatile关键字

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

  1. public class Case08 {
  2. private volatile static Case08 instance;
  3. private Case08() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. public static Case08 getInstance() {
  11. if (instance == null) {
  12. synchronized (Case08.class) {
  13. if (instance == null) {
  14. instance = new Case08();
  15. }
  16. }
  17. }
  18. return instance;
  19. }
  20. }

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 添加了 volatile 关键字修饰,禁止了指令重排序,更安全了一些

  • 缺点:锁所用于代码块。第一次进入都要一次加锁判断,之后进入不需要加锁,但是有可能会出问题

  • 总结:实际开发中使用这种方式

静态内部类 线程安全的

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

  1. public class Case09 {
  2. private Case09() {
  3. try {
  4. TimeUnit.SECONDS.sleep(1);
  5. } catch (InterruptedException e) {
  6. e.printStackTrace();
  7. }
  8. }
  9. static class Inner {
  10. private static Case09 instance = new Case09();
  11. }
  12. public static Case09 getInstance() {
  13. return Inner.instance;
  14. }
  15. }

优缺点

  • 优点:是线程安全的,跟随类加载而加载

  • 缺点:可能会创建了对象不使用,浪费空间

枚举 绝对安全的

步骤

  • 枚举类只有一个实例
  • 暴漏一个外界访问的方法

代码实现

  1. public enum Case10{
  2. INSTANCE;
  3. public static Case10 getInstance() {
  4. return INSTANCE;
  5. }
  6. }

优缺点

  • 缺点:是饿汉式可能浪费空间
  • 优点:绝对安全

总结

  • 单例模式保证了 系统内存种只有该类的一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能

  • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

  • 单例模式使用的场景:需要频繁的进行创建和销毁的对象,创建对象的时候消耗过多或耗费资源过多(也就是:重量级对象) 但又是经常用到的对象,工具类对象,频繁访问数据库或文件的对象(比如数据源.session工厂等)

  • 对于非枚举实现的单例设计模式,可以通过反射创建对象,但是一般不会这么做,而枚举类型通过反射创建对象的时候,会抛异常

  • 推荐使用双重检查锁+Volatile关键字