什么是单例模式?

Intend:Ensure a class only has one instance, and provide a global point of access to it.

目标:保证一个类只有一个实例,并提供全局访问点

--------(《设计模式:可复用面向对象软件的基础》

就运行机制来说,就是一个类,在运行过程中只存在一份内存空间,外部的对象想使用它,都只会调用那部分内存。

其目的有实现唯一控制访问,节约资源,共享单例实例数据。

单例基础实现有两种思路,

1.Eager initialization:在加载类时构造;2.Lazy Initialization:在类使用时构造;3

1.Eager initialization适用于高频率调用,其由于预先创建好Singleton实例会在初始化时使用跟多时间,但在获得实例时无额外开销

其典型代码如下:

  1. public class EagerInitSingleton {
  2. //构建实例
  3. private static final EagerInitSingleton SINGLE_INSTANCE = new EagerInitSingleton();
  4. //私有化构造器
  5. private EagerInitSingleton(){}
  6. //获得实例
  7. public static EagerInitSingleton getInstance(){
  8. return SINGLE_INSTANCE;
  9. }
  10. }

换一种思路,由于类内静态块也只在类加载时运行一次,所以也可用它来代替构造单例:

  1. public class EagerInitSingleton {
  2. //构建实例
  3. //private static final EagerInitSingleton instance = new EagerInitSingleton();
  4. //此处不构造
  5. private static StaticBlockSingleton instance;
  6.  
  7. //使用静态块构造
  8. static{
  9. try{
  10. instance = new StaticBlockSingleton();
  11. }catch(Exception e){
  12. throw new RuntimeException("Exception occured in creating singleton instance");
  13. }
  14. }
  15.  
  16. //私有化构造器
  17. private EagerInitSingleton(){}
  18. //获得实例
  19. public static EagerInitSingleton getInstance(){
  20. return instance;
  21. }
  22. }

2.Lazy Initialization适用于低频率调用,由于只有使用时才构建Singleton实例,在调用时会有系列判断过程所以会有额外开销

2.1Lazy Initialization单线程版

其初步实现如下:

  1. public class LazyInitSingleton {
  2.  
  3. private static LazyInitSingleton SINGLE_INSTANCE = null;
  4.  
  5. //私有化构造器
  6. private LazyInitSingleton() {}
  7.  
  8. //构造实例
  9. public static LazyInitSingleton getInstance() {
  10. if (SINGLE_INSTANCE == null) {
  11. SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
  12. }
  13. return SINGLE_INSTANCE;
  14. }
  15. }

2.2Lazy Initialization多线程版

在多线程下,可能多个线程在较短时间内一同调用 getInstance()方法,且判断

  1. SINGLE_INSTANCE == null

结果都为true,则2.1Lazy Initialization单线程版 会构造多个实例,即单例模式失效

作为修正

2.2.1synchronized关键字第一版

可考虑使用synchronized关键字同步获取方法

  1. public class LazyInitSingleton {
  2.  
  3. private static LazyInitSingleton SINGLE_INSTANCE = null;
  4.  
  5. //私有化构造器
  6. private LazyInitSingleton() {}
  7.  
  8. //构造实例,加入synchronized关键字
  9. public static synchronized LazyInitSingleton getInstance() {
  10. if (SINGLE_INSTANCE == null) {
  11. SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
  12. }
  13. return SINGLE_INSTANCE;
  14. }
  15. }

2.2.2synchronized关键字第二版(double checked locking 二次判断锁)

以上可实现线程安全,但由于使用了synchronized关键字实现锁定控制,getInstance()方法性能下降,造成瓶颈。分析到需求构建操作只限于未构建判断后第一次调用getInstance()方法,即构建为低频操作,所以完全可以在判断已经构建后直接返回,而不需要使用锁,仅在判断需要构建后才进行锁定:

  1. public class LazyInitSingleton {
  2.  
  3. private static LazyInitSingleton SINGLE_INSTANCE = null;
  4.  
  5. //私有化构造器
  6. private LazyInitSingleton() {}
  7.  
  8. //构造实例
  9. public static synchronized LazyInitSingleton getInstance() {
  10. if (SINGLE_INSTANCE == null) {
  11. synchronized(LazyInitSingleton.class){
  12. if (SINGLE_INSTANCE == null) {
  13. SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
  14. }
  15. }
  16. }
  17. return SINGLE_INSTANCE;
  18. }
  19. }

3利用静态内部类实现懒加载

JVM仅在使用时加载静态资源,当类加载时,静态内部类不会加载,仅当静态内部类在使用时会被加载,且实现顺序初始化即加载是线程安全的,利用这一性质,我们可以实现懒加载

  1. public class NestedSingleton {
  2.  
  3. private NestedSingleton() {}
  4. //静态内部类,只初始化一次
  5. private static class SingletonClassHolder {
  6. static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
  7. }
  8. //调用静态内部类方法得到单例
  9. public static NestedSingleton getInstance() {
  10. return SingletonClassHolder.SINGLE_INSTANCE;
  11. }
  12.  
  13. }

4.使用Enum

由于枚举类是线程安全的,可以直接使用枚举创建单例。但考虑到枚举无法继承,所以只在特定情况下使用

  1. public enum EnumSingleton {
  2.  
  3. INSTANCE;
  4. }

附1 利用反射机制破解单例(非Enum形式的单例,)

单例形式的类可被反射破解,从而使用单例失效,即将其Private构造器,通过反射形式暴露出来,并进行实例的构造

  1. public static void main(String[] args) {
  2.  
  3. NestedSingleton nestedSingleton = NestedSingleton.getInstance();
  4. NestedSingleton nestedSingleton2 = null;
  5. try {
  6. //暴露构造器
  7. Constructor[] constructors = nestedSingleton.getClass().getDeclaredConstructors();
  8. Constructor constructor = constructors[1];
  9. constructor.setAccessible(true);
  10. nestedSingleton2 = (NestedSingleton)constructor.newInstance();
  11.  
  12. } catch (Exception e ) {
  13. // TODO Auto-generated catch block
  14. e.printStackTrace();
  15. }
  16. System.out.println(nestedSingleton.hashCode());
  17. System.out.println(nestedSingleton2.hashCode());
  18. }
  19. }

为防止以上情况,可在构造器中抛出异常,以阻止新的实例产生

  1. public class NestedSingleton {
  2.  
  3. private NestedSingleton() {
  4. synchronized (NestedSingleton.class) {
  5. //判断是否已有实例
  6. if(SingletonClassHolder.SINGLE_INSTANCE != null){
  7. throw new RuntimeException("new another instance!");
  8. }
  9. }
  10.  
  11. }
  12. //静态内部类,只初始化一次
  13. private static class SingletonClassHolder {
  14. static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
  15.  
  16. }
  17. //调用静态内部类方法得到单例
  18. public static NestedSingleton getInstance() {
  19. return SingletonClassHolder.SINGLE_INSTANCE;
  20. }
  21. }

附2 序列化(Serialization)导致的单例失效

  1. import java.io.FileInputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import java.io.ObjectInput;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutput;
  7. import java.io.ObjectOutputStream;
  8. import java.io.OutputStream;
  9.  
  10. public class SingletonSerializedTest {
  11.  
  12. public static void main(String[] args) throws Exception {
  13.  
  14. NestedSingleton nestedSingleton0 = NestedSingleton.getInstance();
  15. ObjectOutput output =null;
  16. OutputStream outputStream = new FileOutputStream("serializedFile.ser");
  17. output = new ObjectOutputStream(outputStream);
  18. output.writeObject(nestedSingleton0);
  19. output.close();
  20.  
  21. ObjectInput input = new ObjectInputStream(new FileInputStream("serializedFile.ser"));
  22. NestedSingleton nestedSingleton1 = (NestedSingleton) input.readObject();
  23. input.close();
  24.  
  25. System.out.println("nestedSingleton0 hashCode="+nestedSingleton0.hashCode());
  26. System.out.println("nestedSingleton1 hashCode="+nestedSingleton1.hashCode());
  27.  
  28. }
  29. }

输出结果

  1. nestedSingleton0 hashCode=865113938
  2. nestedSingleton1 hashCode=2003749087

显然,产生了两个实例

如果要避免这个情况,则需要利用ObjectInputStream.readObject()中的机制,它在调用readOrdinaryObject()后会判断类中是否有ReadResolve()方法,如果有就采用类中的ReadResolve()新建实例

那么以上单例类就可以如下改造

  1. import java.io.Serializable;
  2.  
  3. public class NestedSingleton implements Serializable {
  4.  
  5. /**
  6. *
  7. */
  8. private static final long serialVersionUID = 3934012982375502226L;
  9. private NestedSingleton() {
  10. synchronized (NestedSingleton.class) {
  11. //判断是否已有实例
  12. if(SingletonClassHolder.SINGLE_INSTANCE != null){
  13. throw new RuntimeException("new another instance!");
  14. }
  15. }
  16. }
  17. //静态内部类,只初始化一次
  18. private static class SingletonClassHolder {
  19. static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
  20. }
  21. //调用静态内部类方法得到单例
  22. public static NestedSingleton getInstance() {
  23. return SingletonClassHolder.SINGLE_INSTANCE;
  24. }
  25. public void printFn() {
  26. // TODO Auto-generated method stub
  27. System.out.print("fine");
  28. }
  29. protected Object readResolve() {
  30. return getInstance();
  31. }
  32. }

再次使用序列化反序列化过程验证,得到

  1. nestedSingleton0 hashCode=865113938
  2. nestedSingleton1 hashCode=865113938

这样在序列化反序列化过程保证了单例的实现

Java Singleton(单例模式) 实现详解的更多相关文章

  1. Java编程配置思路详解

    Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...

  2. Java 8 Stream API详解--转

    原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java8引入了 ...

  3. java反射机制深入详解

    java反射机制深入详解  转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...

  4. 国际化,java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  5. java之StringBuffer类详解

    StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...

  6. java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  7. java之AbstractStringBuilder类详解

    目录 AbstractStringBuilder类 字段 构造器 方法   public abstract String toString() 扩充容量 void  expandCapacity(in ...

  8. java之StringBuilder类详解

    StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...

  9. java.util.ResourceBundle使用详解(转)

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

随机推荐

  1. odoo:开源ERP/安装和初始设置

    1.1 Odoo的结构 Odoo使用Web浏览器来访问Odoo服务,因此你的Odoo服务器可以部署在较远的地方(如另外一个城市),用户的计算机上只需安装谷歌.火狐或 IE9 以上的浏览器,所以Web客 ...

  2. 雨天的尾巴(bzoj3307)(线段树合并+树上差分)

    \(N\)个点,形成一个树状结构.有\(M\)次发放,每次选择两个点\(x,y\) 对于\(x\)到\(y\)的路径上(含\(x,y\))每个点发一袋\(Z\)类型的物品.完成 所有发放后,每个点存放 ...

  3. MySQL(分组、连表操作、备份数据库)

    day58 分组 参考:https://www.cnblogs.com/xp796/p/5262187.html select dept, max(salary) from department gr ...

  4. echart 遇到的点

    1,图表随着外部container变化而变化: window.onresize = myChart.resize (拿着resize在api文档中搜就看到了)

  5. 缓存行和cpu缓存实例

    并发框架Disruptor译文 剖析Disruptor:为什么会这么快?(一)锁的缺点 剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充 剖析Disruptor:为什么会这么快?(三)伪 ...

  6. python pip安装模块提示错误failed to create process

    python pip安装模块提示错误failed to create process 原因: 报这个错误的原因,是因为python的目录名称或位置发生改动. 解决办法: 1.找到修改python所在的 ...

  7. thuwc2019总结

    275,是我的自己的估分 而350,是面试线 就发挥而言,这次的发挥相当糟糕,第一天选择全场打暴力而不打签到题正解,第二天因A题思路想偏造成2h额外时间花费.第二题与第三题之间,我选择了难打的第三题而 ...

  8. LeetCode All in One题解汇总(持续更新中...)

    突然很想刷刷题,LeetCode是一个不错的选择,忽略了输入输出,更好的突出了算法,省去了不少时间. dalao们发现了任何错误,或是代码无法通过,或是有更好的解法,或是有任何疑问和建议的话,可以在对 ...

  9. Vue.js系列之二Vue实例

    每个Vue应用都是通过Vue函数创建一个新的Vue实例开始,代码如下: var vm=new Vue({}); {}是创建Vue应用时的参数对象 1.Vue实例的data属性 当一个Vue对象被创建时 ...

  10. Vue的实时时间转换Demo

    Vue的实时时间转换Demo time.html: <!DOCTYPE html> <html lang="en"> <head> <me ...