分析java中全面的单例模式多种实现方式

作者:SharpCJ 时间:2021-12-28 05:40:29 

一、单例模式的思想

想整理一些 java 并发相关的知识,不知道从哪开始,想起了单例模式中要考虑的线程安全,就从单例模式开始吧。以前写过单例模式,这里再重新汇总补充整理一下,单例模式的多种实现。

单例模式的主要思想是:

  • 将构造方法私有化( 声明为 private ),这样外界不能随意 new 出新的实例对象;

  • 声明一个私有的静态的实例对象,供外界使用;

  • 提供一个公开的方法,让外界获得该类的实例对象

这种说法看上去没错,但也好像不太准确。其实,就算外界能随意 new 出新的实例对象,但只要我们保证我们每次使用的对象是唯一的,就可以。

二、单例模式的 N 种实现方式

2.1、饿汉式(线程安全,可用)


public class Singleton {
   private Singleton() {
   }

private static Singleton sSingleton = new Singleton();

public static Singleton getInstance() {
       return sSingleton;
   }
}
  • 缺点: 类一加载的时候,就实例化,提前占用了系统资源。

2.2、常量式(线程安全,可用)


public class Singleton {
   private Singleton() {
   }

public static final Singleton sSingleton = new Singleton();
}

将实例对象用 public static final 修饰,不提供公开方法获取实例,直接通过 Singleton.sSingleton 获取。

  • 缺点:与饿汉式一样,类一加载的时候,就实例化,提前占用了系统资源。

2.3、懒汉式(线程不安全,并发场景不可用)


public class Singleton {
   private Singleton() {
   }

private static Singleton sSingleton;

public static Singleton getInstance() {
       if (sSingleton == null) {
           sSingleton = new Singleton();
       }
       return sSingleton;
   }
}
  • 缺点:第一次第一次加载时反应稍慢,线程不安全。

2.4、同步的懒汉式?(线程安全,可用,不建议使用)


public class Singleton {
   private Singleton() {
   }

private static Singleton sSingleton;

public synchronized static Singleton getInstance() {
       if (sSingleton == null) {
           sSingleton = new Singleton();
       }
       return sSingleton;
   }
}
  • 缺点:第一次加载时反应稍慢,每次调用 getInstance 都进行同步,造成不必要的同步开销,这种模式一般不建议使用。

2.5、双重检查锁 DCL (线程安全,大多数场景满足需求,推荐使用)


public class Singleton {
   private Singleton() {
   }

/**
    * volatile is since JDK5
    */
   private static volatile Singleton sSingleton;

public static Singleton getInstance() {
       if (sSingleton == null) {
           synchronized (Singleton.class) {
               // 未初始化,则初始instance变量
               if (sSingleton == null) {
                   sSingleton = new Singleton();
               }
           }
       }
       return sSingleton;
   }
}

sSingleton = new Singleton() 不是一个原子操作。(XXX)故须加 volatile 关键字修饰,该关键字在 jdk1.5 之后版本才有。

  • 优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。

  • 缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,否则这种方式一般能够满足需求。

2.6、静态内部类(线程安全,推荐使用)


public class Singleton {

private Singleton () {
   }

private static class InnerClassSingleton {
private final static Singleton sSingleton = new Singleton();
   }

public static Singleton getInstance() {
       return InnerClassSingleton.sSingleton;
   }
}

优点:推荐使用。

2.7、枚举单例(线程安全,不建议使用)


public enum Singleton{
   INSTANCE;

// 其它方法
   public void doSomething(){
       ...
   }
}
  • 优点:枚举实现单例很简单,也很安全。

  • 缺点:经验丰富的 Android 开发人员都会尽量避免使用枚举。官方文档有说明:相比于静态常量Enum会花费两倍以上的内存。

2.8、另类实现——利用容器实现单例


import java.util.HashMap;
import java.util.Map;

public class Singleton {
   private static Map<String, Object> objMap = new HashMap<String, Object>();

private Singleton() {
   }

public static void registerService(String key, Object instance) {
       if (!objMap.containsKey(key)) {
           objMap.put(key, instance);
       }
   }

public static Object getService(String key) {
       return objMap.get(key);
   }
}

利用了 HashMap 容器 key 不可重复的特性。

  • 优点:这种实现方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一接口进行获取操作,降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。

  • 缺点:没有私有化构造方法,用户可以 new 出新的实例对象。

2.9、防止反射破坏单例

前面的多种实现方法中,很多我们按照构造方法私有化的思想来实现的,我们知道,利用反射,仍然可以创建出新对象,这样在反射场景中,这种思想实现的单例模式就失效了,那么如何防止反射破坏单例模式呢?原理上就是在存在一个实例的情况下,再次调用构造方法时,抛出异常。下面以静态内部类的单例模式为例:


public class Singleton {
   private static boolean flag = false;  

private Singleton(){  
       synchronized(Singleton.class)  
       {  
           if(flag == false)  
           {  
               flag = !flag;  
           }  
           else  
           {  
               throw new RuntimeException("单例模式被侵犯!");  
           }  
       }  
   }  

private static class InnerClassSingleton {
private final static Singleton sSingleton = new Singleton();
   }

public static Singleton getInstance() {
       return InnerClassSingleton.sSingleton;
   }
}

2.10、防止序列化和反序列化破坏单例

通过序列化可以讲一个对象实例写入到磁盘中,通过反序列化再读取回来的时候,即便构造方法是私有的,也依然可以通过特殊的途径,创建出一个新的实例,相当于调用了该类的构造函数。要避免这个问题,我们需要在代码中加入如下方法,让其在反序列化过程中执行 readResolve 方法时返回 sSingleton 对象。


private Object readResolve() throws ObjectStreamException {
   return sSingleton;
}

三、结语

有没有一种方式实现的单例模式在任何情况下都是一个单例呢?

有。就是上面说的枚举单例。枚举,就能保证在任何情况下都是单例的,并且是线程安全的。

来源:https://www.cnblogs.com/joy99/p/9859764.html

标签:java,单例模式,单例,实现方式
0
投稿

猜你喜欢

  • Struts2拦截器登录验证实例

    2021-10-26 06:47:04
  • Java多文件以ZIP压缩包导出的实现方法

    2023-10-08 14:05:20
  • Spring boot随机端口你都不会还怎么动态扩容

    2021-09-29 10:10:14
  • Android 去掉状态栏的方法汇总

    2022-05-25 01:31:18
  • SpringBoot 在项目启动之后执行自定义方法的两种方式小结

    2021-05-25 15:46:36
  • IDEA中设置代码自动提示为Alt+/的具体做法

    2022-07-06 14:58:32
  • Android关于Button背景或样式失效问题解决方法

    2021-12-28 03:21:06
  • C#中实现判断某个类是否实现了某个接口

    2022-12-31 19:39:45
  • Java Socket编程实例(一)- TCP基本使用

    2023-11-11 08:42:50
  • Spring Boot和Vue前后端分离项目架构的全过程

    2023-09-20 17:49:13
  • Java 替换word文档文字并指定位置插入图片

    2023-08-12 22:02:25
  • Android界面刷新的方法分享

    2022-06-20 15:20:52
  • Android 高仿微信朋友圈动态支持双击手势放大并滑动查看图片效果

    2021-08-21 21:16:40
  • 基于SpringMVC入门案例及讲解

    2023-04-06 17:34:43
  • mybatis中使用大于小于等于的正确方法

    2021-10-09 03:52:56
  • Java设计模式编程之解释器模式的简单讲解

    2022-01-24 16:03:32
  • Android模块化中数据传递/路由跳转实现示例

    2023-06-29 03:12:06
  • Android开发实现的计时器功能示例

    2023-09-04 07:33:33
  • 基于java中cookie和session的比较

    2021-08-17 00:49:44
  • shiro无状态web集成的示例代码

    2021-10-13 06:15:46
  • asp之家 软件编程 m.aspxhome.com