JAVA单例模式 关于延迟加载问题
设计模式
首先要解释一下什么是延迟加载,延迟加载就是等到真真使用的时候才去创建实例,不用时不要去创建。
从速度和反应时间角度来讲,非延迟加载(又称饿汉式)好;从资源利用效率上说,延迟加载(又称懒汉式)好。
下面看看几种常见的单例的设计方式:
第一种:非延迟加载单例类
Java代码
public class Singleton { private Singleton() {} private static final Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }
第二种:同步延迟加载
Java代码
public class Singleton { private static Singleton instance = null; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
第三种:双重检测同步延迟加载
为处理原版非延迟加载方式瓶颈问题,我们需要对 instance 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了),但在Java 中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。JDK5.0以后版本若instance为volatile则可行:
Java代码
public class Singleton { private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) {// 1 if (instance == null) {// 2 instance = new Singleton();// 3 } } } return instance; } }
双重检测锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是失败的一个主要原因。
无序写入:
为 解释该问题,需要重新考察上述清单中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 instance 来引用此对象。这行代码的问题是:在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还会初始化的对象,这样会导致系统崩溃。
什么?这一说法可能让您始料未及,但事实确实如此。在解释这个现象如何发生前,请先暂时接受这一事实,我们先来考察一下双重检查锁定是如何被破坏的。假设代码执行以下事件序列:
1、线程 1 进入 getInstance() 方法。
2、由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
3、线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。
4、线程 1 被线程 2 预占。
5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。
6、线程 2 被线程 1 预占。
7、线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
为展示此事件的发生情况,假设代码行 instance =new Singleton(); 执行了下列伪代码:
mem = allocate(); //为单例对象分配内存空间.
instance = mem; //注意,instance 引用现在是非空,但还未初始化
ctorSingleton(instance); //为单例对象通过instance调用构造函数
这段伪代码不仅是可能的,而且是一些 JIT 编译器上真实发生的。执行的顺序是颠倒的,但鉴于当前的内存模型,这也是允许发生的。JIT 编译器的这一行为使双重检查锁定的问题只不过是一次学术实践而已。
如果真像这篇文章:http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html所说那样的话,1.2或以后的版本就不会有问题了,但这个规则是JMM的规范吗?谁能够确认一下。
确实,在JAVA2(以jdk1.2开始)以前对于实例字段是直接在主储区读写的.所以当一个线程对resource进行分配空间,
初始化和调用构造方法时,可能在其它线程中分配空间动作可见了,而初始化和调用构造方法还没有完成.
但是从JAVA2以后,JMM发生了根本的改变,分配空间,初始化,调用构造方法只会在线程的工作存储区完成,在没有
向主存储区复制赋值时,其它线程绝对不可能见到这个过程.而这个字段复制到主存区的过程,更不会有分配空间后
没有初始化或没有调用构造方法的可能.在JAVA中,一切都是按引用的值复制的.向主存储区同步其实就是把线程工作
存储区的这个已经构造好的对象有压缩堆地址值COPY给主存储区的那个变量.这个过程对于其它线程,要么是resource
为null,要么是完整的对象.绝对不会把一个已经分配空间却没有构造好的对象让其它线程可见.
另一篇详细分析文章:http://www.iteye.com/topic/260515
第四种:使用ThreadLocal修复双重检测
借助于ThreadLocal,将临界资源(需要同步的资源)线程局部化,具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为了线程局部范围内来作。这里的ThreadLocal也只是用作标示而已,用来标示每个线程是否已访问过,如果访问过,则不再需要走同步块,这样就 提高了一定的效率。但是ThreadLocal在1.4以前的版本都较慢,但这与volatile相比却是安全的。
Java代码
public class Singleton { private static final ThreadLocal perThreadInstance = new ThreadLocal(); private static Singleton singleton ; private Singleton() {} public static Singleton getInstance() { if (perThreadInstance.get() == null){ // 每个线程第一次都会调用 createInstance(); } return singleton; } private static final void createInstance() { synchronized (Singleton.class) { if (singleton == null){ singleton = new Singleton(); } } perThreadInstance.set(perThreadInstance); } }
第五种:使用内部类实现延迟加载
为了做到真真的延迟加载,双重检测在Java中是行不通的,所以只能借助于另一类的类加载加延迟加载:
Java代码
public class Singleton { private Singleton() {} public static class Holder { // 这里的私有没有什么意义 /* private */static Singleton instance = new Singleton(); } public static Singleton getInstance() { // 外围类能直接访问内部类(不管是否是静态的)的私有变量 return Holder.instance; } }
单例测试
下面是测试单例的框架,采用了类加载器与反射。
注,为了测试单便是否为真真的单例,我自己写了一个类加载器,且其父加载器设置为根加载器, 这样确保Singleton由MyClassLoader加载,如果不设置为根加载器为父加载器,则默认为系统加载器,则Singleton会由系统加载 器去加载,但这样我们无法卸载类加载器,如果加载Singleton的类加载器卸载不掉的话,那么第二次就不能重新加载Singleton的Class 了,这样Class不能得加载则最终导致Singleton类中的静态变量重新初始化,这样就无法测试了。
下面测试类延迟加载的结果是可行的,同样也可用于其他单例的测试:
Java代码
public class Singleton { private Singleton() {} public static class Holder { // 这里的私有没有什么意义 /* private */static Singleton instance = new Singleton(); } public static Singleton getInstance() { // 外围类能直接访问内部类(不管是否是静态的)的私有变量 return Holder.instance; } } class CreateThread extends Thread { Object singleton; ClassLoader cl; public CreateThread(ClassLoader cl) { this.cl = cl; } public void run() { Class c; try { c = cl.loadClass("Singleton"); // 当两个不同命名空间内的类相互不可见时,可采用反射机制来访问对方实例的属性和方法 Method m = c.getMethod("getInstance", new Class[] {}); // 调用静态方法时,传递的第一个参数为class对象 singleton = m.invoke(c, new Object[] {}); c = null; cl = null; } catch (Exception e) { e.printStackTrace(); } } } class MyClassLoader extends ClassLoader { private String loadPath; MyClassLoader(ClassLoader cl) { super(cl); } public void setPath(String path) { this.loadPath = path; } protected Class findClass(String className) throws ClassNotFoundException { FileInputStream fis = null; byte[] data = null; ByteArrayOutputStream baos = null; try { fis = new FileInputStream(new File(loadPath + className.replaceAll("\\.", "\\\\") + ".class")); baos = new ByteArrayOutputStream(); int tmpByte = 0; while ((tmpByte = fis.read()) != -1) { baos.write(tmpByte); } data = baos.toByteArray(); } catch (IOException e) { throw new ClassNotFoundException("class is not found:" + className, e); } finally { try { if (fis != null) { fis.close(); } if (fis != null) { baos.close(); } } catch (Exception e) { e.printStackTrace(); } } return defineClass(className, data, 0, data.length); } } class SingleTest { public static void main(String[] args) throws Exception { while (true) { // 不能让系统加载器直接或间接的成为父加载器 MyClassLoader loader = new MyClassLoader(null); loader .setPath("D:\\HW\\XCALLC16B125SPC003_js\\uniportal\\service\\AAA\\bin\\"); CreateThread ct1 = new CreateThread(loader); CreateThread ct2 = new CreateThread(loader); ct1.start(); ct2.start(); ct1.join(); ct2.join(); if (ct1.singleton != ct2.singleton) { System.out.println(ct1.singleton + " " + ct2.singleton); } // System.out.println(ct1.singleton + " " + ct2.singleton); ct1.singleton = null; ct2.singleton = null; Thread.yield(); } } }
转载于:https://www.cnblogs.com/sky-of-chuanqingchen/p/4988671.html
JAVA单例模式 关于延迟加载问题相关推荐
- Java——单例模式和延迟加载
延迟加载 延迟加载(lazy load) (也称为懒加载,也叫延迟实例化,延迟初始化等)主要表达的思想就是:把对象的创建延迟到使用的时候创建,而不是对象实例化的时候创建.延迟加载机制是为了避免一些无谓 ...
- Java 单例模式:懒加载(延迟加载)和即时加载
Java 单例模式:懒加载(延迟加载)和即时加载 引言 在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制(或懒加载.延时加载),也就是说只有当使用到这个实例的时候才会创 ...
- Java 单例模式探讨
以下是我再次研究单例(Java 单例模式缺点)时在网上收集的资料,相信你们看完就对单例完全掌握了 Java单例模式应该是看起来以及用起来简单的一种设计模式,但是就实现方式以及原理来说,也并不浅显哦. ...
- 什么是java单例模式?
关于java单例模式的文章早已是非常多了,本文是对我个人过往学习java,理解及应用java单例模式的一个总结.此文内容涉及java单例模式的基本概念,以及什单例模式的优缺点,希望对大家有所帮助. 什 ...
- 比心app源码,Java 单例模式
比心app源码,Java 单例模式实现的相关代码 概述:单例模式是指在内存中永远只有一个类的实例. 有利于节约内存和保证共享计算的结果正确,方便管理. 单例模式的形式 饿汉式单例:在获取单例对象之前对 ...
- Java单例模式及开发应用场景
一.Java单例模式是什么? 所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例. 二.为什么要用单例模式? 单例有其独有的使用场景,一般是对于那些业 ...
- Java单例模式个人总结(实例变量和类变量)
Java单例模式 背景知识:Static关键字. 在对于定义类的变量,分为两种,是否具有static修饰的变量: 没有static修饰的变量,通过类的实例化(对象)引用,改变量称为实例变量: 使用st ...
- Java单例模式优化写法
转载自 http://blog.csdn.net/diweikang/article/details/51354982 Java单例模式优化写法 方法一:推荐 [java] view plain co ...
- Java单例模式的几种实现方式
Java单例模式的几种实现方式 在Java 中,单例类只能有一个实例,必须创建自己的唯一实例,单例类必须给所有其他对象提供这一实例.Java 单例模式有很多种实现方式,在这里给大家介绍单例模式其中的几 ...
最新文章
- java程序设计实验报告_JavA程序设计实验报告.doc
- 人工智能呼唤社会科学家
- .net一个函数要用另一个函数的值_MATLAB中的神经网络工具箱(2)函数命令及模型搭建...
- debian jessie install note
- softmax函数与交叉熵损失函数
- linux桌面lxde 安装_观点|最新精简型 Linux 桌面环境大比拼:LXDE Vs. Xfce Vs. MATE
- 一加6html查看程序,一加6T评测:极速屏下指纹,解锁你想要的轻快顺滑
- 百度 (baidu) 举办了一场全公司范围内的 拳皇友谊赛
- wms智能仓储系统不可缺少?
- Android逆向基础笔记—Android中的常用ARM汇编指令
- 如何使用Movavi Slideshow Maker进行幻灯片制作?
- mysql 命令连接,授权用户
- 杰·亚伯拉罕的产品营销35种策略完整版
- linux中的段定义的,Linux中的段
- 2021年3月最新-李沐-动手学深度学习第二版-中、英文版
- Tesseract怎么识别中文
- 一个10年符号主义学者的深度讨论:如何理性看待ChatGPT?
- Tableau疫情可视化
- java对接杉德支付完整代码
- scratch——画板
热门文章
- 计算机专业学的东西其实很少,为什么计算机专业的学生要学习使用Linux系统?...
- asp隐藏邮箱部分字符_asp.net core 中使用 signalR(二)
- Android接入unityads广告,Unity Ads胡敏:开发者如何通过广告获取成功
- python yaml
- python 链接数据库
- java ajax 联动菜单_java结合jQuery.ajax实现左右菜单联动刷新列表内容
- ESXi 中重新启动管理代理
- 一张图来帮你理解 SOA(转发)
- Maven学习总结(12)——eclipse中构建多模块maven项目
- python绘制图像直方图_Python – 计算图像的直方图