单例模式

Java内存模型的抽象示意图:

所有单例模式都有一个共性,那就是这个类没有自己的状态。也就是说无论这个类有多少个实例,都是一样的;然后除此者外更重要的是,这个类如果有两个或两个以上的实例的话程序会产生错误。

非线程安全的模式

public class Singleton {private static Singleton instance;private Singleton(){}public static Singleton getInstance() {if (instance == null) //1:A线程执行instance = new Singleton(); //2:B线程执行return instance;}
}

普通加锁

public class SafeLazyInitialization {private static Singleton instance;public synchronized static Singleton getInstance() {if (instance == null)instance = new Singleton();return instance;}
}

出于性能考虑,采用双重检查加锁的模式

双重检查加锁模式

public class Singleton{private static Singleton singleton;private Singleton(){}public static Singleton getInstance(){if(null == singleton){  //第一次检查synchronized(Singleton.class){  //加锁if(null == singleton){  //第二次检查singleton = new Singleton();//问题的根源出在这里}}}return singleton;}
}

双重检查加锁模式相对于普通的单例和加锁模式而言,从性能和线程安全上来说都有很大的提升和保障。然而双重检查加锁模式也存在一些隐蔽不易被发现的问题。首先我们要明白在JVM创建新的对象时,主要要经过三个步骤。

  • 分配内存
  • 初始化构造器
  • 将对象指向分配的内存地址

这样的顺序在双重加锁模式下是么有问题的,对象在初始化完成之后再把内存地址指向对象。

问题的根源

但是现代的JVM为了追求执行效率会针对字节码(编译器级别)以及指令和内存系统重排序(处理器重排序)进行调优,这样的话就有可能(注意是有可能)导致2和3的顺序是相反的,一旦出现这样的情况问题就来了。

java源代码到最终实际执行的指令序列:

前面的双重检查锁定示例代码的(instance = new Singleton();)创建一个对象。这一行代码可以分解为如下的三行伪代码:

memory = allocate();   //1:分配对象的内存空间
ctorInstance(memory);  //2:初始化对象
instance = memory;     //3:设置instance指向刚分配的内存地址

上面三行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的,详情见参考文献1的“Out-of-order writes”部分)。2和3之间重排序之后的执行时序如下:

memory = allocate();   //1:分配对象的内存空间
instance = memory;     //3:设置instance指向刚分配的内存地址//注意,此时对象还没有被初始化!
ctorInstance(memory);  //2:初始化对象

多线程并发执行的时候的情况:

解决方案

基于Volatile的解决方案

先来说说Volatile这个关键字的含义:

  • 可以很好地解决可见性问题
  • 但不能确保原子性问题(通过 synchronized 进行解决)
  • 禁止指令的重排序(单例主要用到此JVM规范)

Volatile 双重检查加锁模式

public class Singleton{private volatile static Singleton singleton;private Singleton(){}public static Singleton getInstance(){if(null == singleton){synchronized(Singleton.class){if(null == singleton){singleton = new Singleton();}}}return singleton;}
}

基于类初始化的解决方案

利用静态内部类的方式来创建,因为静态属性由JVM确保第一次初始化时创建,因此也不用担心并发的问题出现。当初始化进行到一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以singleton仍然是单例的。

这个方案的实质是:允许“问题的根源”的三行伪代码中的2和3重排序,但不允许非构造线程(这里指线程B)“看到”这个重排序。

静态内部类的方式

public class Singleton{private Singleton(){}public static Singleton getInstance(){return InnerClassSingleton.singleton;}private class InnerClassSingleton{protected static Singleton singleton = new Singleton();}
}

然而,虽然静态内部类模式可以很好地避免并发创建出多个实例的问题,但这种方式仍然有其存在的隐患。

存在的隐患

  • 一旦一个实例被持久化后重新生成的实例仍然有可能是不唯一的。
  • 由于java提供了反射机制,通过反射机制仍然有可能生成多个实例。

序列化和反序列化带来的问题:反序列化后两个实例不一致了。

private static void singleSerializable() {try (FileOutputStream fileOutputStream=new FileOutputStream(new File("myObjectFilee.txt"));ObjectOutputStream objectOutputStream=new ObjectOutputStream(fileOutputStream);) {
//            SingletonObject singletonObject = SingletonObject.getInstance();
//            InnerClassSingleton singletonObject = InnerClassSingleton.getInstance();EnumSingleton singletonObject = EnumSingleton.INSTANCE;objectOutputStream.writeObject(singletonObject);objectOutputStream.close();fileOutputStream.close();System.out.println(singletonObject.hashCode());} catch (IOException e) {e.printStackTrace();}try (FileInputStream fileInputStream=new FileInputStream(new File("myObjectFilee.txt"));ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream);) {//            SingletonObject singleTest=(SingletonObject) objectInputStream.readObject();
//            InnerClassSingleton singleTest=(InnerClassSingleton) objectInputStream.readObject();EnumSingleton singleTest=(EnumSingleton) objectInputStream.readObject();objectInputStream.close();fileInputStream.close();System.out.println(singleTest.hashCode());} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}
}

问题点及解决办法
ObjectInputStream中的readOrdinaryObject

if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod())
{Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}
}

调用自定义的readResolve方法

protected Object readResolve(){System.out.println("调用了readResolve方法!");return  InnerClassSingleton.getInstance();
}

通过反射机制获取到两个不同的实例

private static void attack() {try {Class<?> classType = InnerClassSingleton.class;Constructor<?> constructor = classType.getDeclaredConstructor(null);constructor.setAccessible(true);InnerClassSingleton singleton = (InnerClassSingleton) constructor.newInstance();InnerClassSingleton singleton2 = InnerClassSingleton.getInstance();System.out.println(singleton == singleton2);  //false} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}
}

解决方案 : 私有构造方法中进行添加标志判断。

private InnerClassSingleton() {synchronized (InnerClassSingleton.class) {if (false == flag) {flag = !flag;} else {throw new RuntimeException("单例模式正在被攻击");}}
}

单例最优方案,枚举的方式

枚举实现单例的优势

  • 自由序列化;
  • 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
  • 线程安全;
public enum Singleton {INSTANCE;private Singleton(){}
}

Hibernate的解决方案

通过ThreadLocal的方式

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;
public class HibernateSessionFactory {private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";private static final ThreadLocal threadLocal = new ThreadLocal();private static Configuration configuration = new Configuration();private static org.hibernate.SessionFactory sessionFactory;private static String configFile = CONFIG_FILE_LOCATION;static {try {configuration.configure(configFile);sessionFactory = configuration.buildSessionFactory();} catch (Exception e) {System.err.println("%%%% Error Creating SessionFactory %%%%");e.printStackTrace();}}private HibernateSessionFactory() {}public static Session getSession() throws HibernateException {Session session = (Session) threadLocal.get();if (session == null || !session.isOpen()) {if (sessionFactory == null) {rebuildSessionFactory();}session = (sessionFactory != null) ? essionFactory.openSession() : null;threadLocal.set(session);}return session;}
// Other methods...
}本文转自秋楓博客园博客,原文链接:http://www.cnblogs.com/rwxwsblog/p/6662951.html,如需转载请自行联系原作者

深入分析Java单例模式的各种方案相关推荐

  1. Java单例模式个人总结(实例变量和类变量)

    Java单例模式 背景知识:Static关键字. 在对于定义类的变量,分为两种,是否具有static修饰的变量: 没有static修饰的变量,通过类的实例化(对象)引用,改变量称为实例变量: 使用st ...

  2. 深入分析 Java I/O 的工作机制

    深入分析 Java I/O 的工作机制 I/O 问题是任何编程语言都无法回避的问题,可以说 I/O 问题是整个人机交互的核心问题,因为 I/O 是机器获取和交换信息的主要渠道.在当今这个数据大爆炸时代 ...

  3. java内存泄露分析方案

    java内存泄露分析方案 - 准备工作 1.工具:Memory Analyzer Tool (mat); 1)安装Memory Analyzer Tool (mat) 2.原料:dump.hprof ...

  4. 深入分析 Java I/O 的工作机制--转载

    Java 的 I/O 类库的基本架构 I/O 问题是任何编程语言都无法回避的问题,可以说 I/O 问题是整个人机交互的核心问题,因为 I/O 是机器获取和交换信息的主要渠道.在当今这个数据大爆炸时代, ...

  5. Java 单例模式探讨

    以下是我再次研究单例(Java 单例模式缺点)时在网上收集的资料,相信你们看完就对单例完全掌握了 Java单例模式应该是看起来以及用起来简单的一种设计模式,但是就实现方式以及原理来说,也并不浅显哦. ...

  6. 深入分析Java Web技术内幕pdf

    下载地址:网盘下载 内容简介  · · · · · · <深入分析Java Web技术内幕(修订版)>新增了淘宝在无线端的应用实践,包括:CDN 动态加速.多终端化改造. 多终端Sessi ...

  7. Java单例模式优化写法

    转载自 http://blog.csdn.net/diweikang/article/details/51354982 Java单例模式优化写法 方法一:推荐 [java] view plain co ...

  8. Java Base64 编码解码方案总结

    转载自  Java Base64 编码解码方案总结 Base64是一种能将任意Binary资料用64种字元组合成字串的方法,而这个Binary资料和字串资料彼此之间是可以互相转换的,十分方便.在实际应 ...

  9. Java单例模式的几种实现方式

    Java单例模式的几种实现方式 在Java 中,单例类只能有一个实例,必须创建自己的唯一实例,单例类必须给所有其他对象提供这一实例.Java 单例模式有很多种实现方式,在这里给大家介绍单例模式其中的几 ...

最新文章

  1. 软考之CPU的寻址方式
  2. 神经网络完成芯片设计仅需几小时
  3. OPEN ERP相关财务的基本概念
  4. mpvue开发微信小程序之picker
  5. angr学习笔记(7)(malloc地址单元符号化)
  6. 帝国cms文章页调用当前文章URL如何操作?
  7. c语言程序设计俄罗斯方块PPT,俄罗斯方块游戏:C语言程序设计初步感受
  8. python字符串最大长度_字符串String的最大长度
  9. java实现三级联动查询_jeefast和Mybatis实现三级联动的示例代码
  10. Jquery操作DOM节点
  11. Redis设计与实现-监视器
  12. 百花开放笑声甜_“开源萌宠”庆六一
  13. Hadoop环境搭建(6) -- 克隆
  14. html 转盘抽奖开发,html 大转盘抽奖
  15. php 七牛云 视频加水印
  16. windows audio错误0x80070005
  17. MySql join 详解
  18. 如何使用MDK建立STM32H7双核编译工程
  19. Python random模块(获取随机数)常用方法和使用例子
  20. 两种方法判断是否为移动端访问,跳转到对应wap页面

热门文章

  1. 它又来了!C**HashMap是如何保证线程安全的?会用不就完了?
  2. 拜访了这位小哥的GitHub后,我失眠了!
  3. 腾讯 JDK 11 正式开源,高性能、太牛逼啦!
  4. Java微服务 vs Go微服务,究竟谁更强!?
  5. 数据持久化框架为什么放弃Hibernate、JPA、Mybatis,最终选择JDBCTemplate!
  6. 干掉cms,zgc才是未来
  7. Java finally语句到底是在return之前还是之后执行?
  8. 数据团队做什么,看这篇就够了!
  9. 别再用ls、cat命令了,这有一套全新升级版,简洁快速还易上手
  10. 用AI还原李焕英老照片动态影像