转自http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html
1 /** 2 * 基础的单例模式,Lazy模式,非线程安全 3 * 优点:lazy,初次使用时实例化单例,避免资源浪费 4 * 缺点:1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题 5 * 2、非线程安全。多线程下可能会有多个实例被初始化。 6 * 7 * @author laichendong 8 * @since 2011-12-5 9 */ 10 public class SingletonOne { 11 12 /** 单例实例变量 */ 13 private static SingletonOne instance = null; 14 15 /** 16 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。 17*/ 18 private SingletonOne() { 19 20 } 21 22 /** 23 * 获取单例对象实例 24 * 25 * @return 单例对象 26*/ 27 public static SingletonOne getInstance() { 28 if (instance == null) { // 1 29 instance = new SingletonOne(); // 2 30 } 31 return instance; 32 } 33 34 }
注释中已经有简单的分析了。接下来分析一下关于“非线程安全”的部分。
1、当线程A进入到第28行(#1)时,检查instance是否为空,此时是空的。
2、此时,线程B也进入到28行(#1)。切换到线程B执行。同样检查instance为空,于是往下执行29行(#2),创建了一个实例。接着返回了。
3、在切换回线程A,由于之前检查到instance为空。所以也会执行29行(#2)创建实例。返回。
4、至此,已经有两个实例被创建了,这不是我们所希望的。
怎么解决线程安全问题?
方法一:同步方法。即在getInstance()方法上加上synchronized关键字。这时单例变成了
1 /** 2 * copyright © sf-express Inc 3 */ 4 package com.something.singleton; 5 6 /** 7 * 同步方法 的单例模式,Lazy模式,线程安全 8 * 优点: 9 * 1、lazy,初次使用时实例化单例,避免资源浪费 10 * 2、线程安全 11 * 缺点: 12 * 1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题 13 * 2、每次调用getInstance()都要获得同步锁,性能消耗。 14 * 15 * @author laichendong 16 * @since 2011-12-5 17 */ 18 public class SingletonTwo { 19 20 /** 单例实例变量 */ 21 private static SingletonTwo instance = null; 22 23 /** 24 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。 25 */ 26 private SingletonTwo() { 27 28 } 29 30 /** 31 * 获取单例对象实例 32 * 同步方法,实现线程互斥访问,保证线程安全。 33 * 34 * @return 单例对象 35 */ 36 public static synchronized SingletonTwo getInstance() { 37 if (instance == null) { // 1 38 instance = new SingletonTwo(); // 2 39 } 40 return instance; 41 } 42 43 }
加上synchronized后确实实现了线程的互斥访问getInstance()方法。从而保证了线程安全。但是这样就完美了么?我们看。其实在典型实现里,会导致问题的只是当instance还没有被实例化的时候,多个线程访问#1的代码才会导致问题。而当instance已经实例化完成后。每次调用getInstance(),其实都是直接返回的。即使是多个线程访问,也不会出问题。但给方法加上synchronized后。所有getInstance()的调用都要同步了。其实我们只是在第一次调用的时候要同步。而同步需要消耗性能。这就是问题。
方法二:双重检查加锁Double-checked locking。
其实经过分析发现,我们只要保证 instance = new SingletonOne(); 是线程互斥访问的就可以保证线程安全了。那把同步方法加以改造,只用synchronized块包裹这一句。就得到了下面的代码:
1 public static SingletonThree getInstance() { 2 if (instance == null) { // 1 3 synchronized (SingletonThree.class) { 4 instance = new SingletonThree(); // 2 5 } 6 } 7 return instance; 8 }
这个方法可行么?分析一下发现是不行的!
1、线程A和线程B同时进入//1的位置。这时instance是为空的。
2、线程A进入synchronized块,创建实例,线程B等待。
3、线程A返回,线程B继续进入synchronized块,创建实例。。。
4、这时已经有两个实例创建了。
为了解决这个问题。我们需要在//2的之前,再加上一次检查instance是否被实例化。(双重检查加锁)接下来,代码变成了这样:
1 public static SingletonThree getInstance() { 2 if (instance == null) { // 1 3 synchronized (SingletonThree.class) { 4 if (instance == null) { 5 instance = new SingletonThree(); // 2 6 } 7 } 8 } 9 return instance; 10 }
这样,当线程A返回,线程B进入synchronized块后,会先检查一下instance实例是否被创建,这时实例已经被线程A创建过了。所以线程B不会再创建实例,而是直接返回。貌似!到 此为止,这个问题已经被我们完美的解决了。遗憾的是,事实完全不是这样!这个方法在单核和 多核的cpu下都不能保证很好的工作。导致这个方法失败的原因是当前java平台的内存模型。java平台内存模型中有一个叫“无序写”(out-of- order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。这个问题的关键在上面代码上的第5行:instance = new SingletonThree(); 这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给instance这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。下面我们看一下出问题的过程:
1、线程A进入getInstance()方法。
2、因为此时instance为空,所以线程A进入synchronized块。
3、线程A执行 instance = new SingletonThree(); 把实例变量instance设置成了非空。(注意,实在调用构造方法之前。)
4、线程A退出,线程B进入。
5、线程B检查instance是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回instance的引用。(问题出现了,这时instance的引用并不是SingletonThree的实例,因为没有调用构造方法。)
6、线程B退出,线程A进入。
7、线程A继续调用构造方法,完成instance的初始化,再返回。
好吧,继续努力,解决由“无序写”带来的问题。
1 public static SingletonThree getInstance() { 2 if (instance == null) { 3 synchronized (SingletonThree.class) { // 1 4 SingletonThree temp = instance; // 2 5 if (temp == null) { 6 synchronized (SingletonThree.class) { // 3 7 temp = new SingletonThree(); // 4 8 } 9 instance = temp; // 5 10 } 11 } 12 } 13 return instance; 14 }
解释一下执行步骤。
1、线程A进入getInstance()方法。
2、因为instance是空的 ,所以线程A进入位置//1的第一个synchronized块。
3、线程A执行位置//2的代码,把instance赋值给本地变量temp。instance为空,所以temp也为空。
4、因为temp为空,所以线程A进入位置//3的第二个synchronized块。
5、线程A执行位置//4的代码,把temp设置成非空,但还没有调用构造方法!(“无序写”问题)
6、线程A阻塞,线程B进入getInstance()方法。
7、因为instance为空,所以线程B试图进入第一个synchronized块。但由于线程A已经在里面了。所以无法进入。线程B阻塞。
8、线程A激活,继续执行位置//4的代码。调用构造方法。生成实例。
9、将temp的实例引用赋值给instance。退出两个synchronized块。返回实例。
10、线程B激活,进入第一个synchronized块。
11、线程B执行位置//2的代码,把instance实例赋值给temp本地变量。
12、线程B判断本地变量temp不为空,所以跳过if块。返回instance实例。
好吧,问题终于解决了,线程安全了。但是我们的代码由最初的3行代码变成了现在的一大坨~。于是又有了下面的方法。
方法三:预先初始化static变量。
1 /** 2 * 预先初始化static变量 的单例模式 非Lazy 线程安全 3 * 优点: 4 * 1、线程安全 5 * 缺点: 6 * 1、非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。 7 * 8 * @author laichendong 9 * @since 2011-12-5 10 */ 11 public class SingletonFour { 12 13 /** 单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 */ 14 private static SingletonFour instance = new SingletonFour(); 15 16 /** 17 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。 18 */ 19 private SingletonFour() { 20 21 } 22 23 /** 24 * 获取单例对象实例 25 * 26 * @return 单例对象 27 */ 28 public static SingletonFour getInstance() { 29 return instance; 30 } 31 32 }
看到这个方法,世界又变得清净了。由于java的机制,static的成员变量只在类加载的时候初始化一次,且类加载是线程安全的。所以这个方法实现的单例是线程安全的。但是这个方法却牺牲了Lazy的特性。单例类加载的时候就实例化了。如注释所述:非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。
那到底有没有完美的办法?懒加载,线程安全,代码简单。
方法四:使用内部类。
1 /** 2 * 基于内部类的单例模式 Lazy 线程安全 3 * 优点: 4 * 1、线程安全 5 * 2、lazy 6 * 缺点: 7 * 1、待发现 8 * 9 * @author laichendong 10 * @since 2011-12-5 11 */ 12 public class SingletonFive { 13 14 /** 15 * 内部类,用于实现lzay机制 16 */ 17 private static class SingletonHolder{ 18 /** 单例变量 */ 19 private static SingletonFive instance = new SingletonFive(); 20 } 21 22 /** 23 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。 24 */ 25 private SingletonFive() { 26 27 } 28 29 /** 30 * 获取单例对象实例 31 * 32 * @return 单例对象 33 */ 34 public static SingletonFive getInstance() { 35 return SingletonHolder.instance; 36 } 37 38 }
解释一下,因为java机制规定,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance。
最后,总结一下:
1、如果单例对象不大,允许非懒加载,可以使用方法三。
2、如果需要懒加载,且允许一部分性能损耗,可以使用方法一。(官方说目前高版本的synchronized已经比较快了)
3、如果需要懒加载,且不怕麻烦,可以使用方法二。
4、如果需要懒加载,没有且!推荐使用方法四。
相关推荐
智能制造的数字化工厂规划qytp.pptx
罗兰贝格:德隆人力资源管理体系gltp.pptx
JAVA3D的网络三维技术的设计与实现
Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。
基于J2EE的B2C电子商务系统开发
麦肯锡_xx保险员工培训咨询报告gltp.pptx
JAVA社区网络服务系统
备自投tp.pptx
Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。
JSP高校教务排课管理系统,管理员角色包含以下功能:课程申请管理,课程设置管理,课程情况查看,专业设置查看,排课管理,系办人员管理,教师管理,学生管理,教室管理,班级管理,管理员登录等功能。教师角色包含以下功能:教师角色登录,申请增加课程,学生管理,成绩录入管理,课程安排管理等功能。学生角色包含以下功能:学生角色登录,基本信息查看,选课功能安排,课程表查看,成绩查询等功能。 本项目实现的最终作用是基于JSP高校教务排课管理系统 分为3个角色 第1个角色为管理员角色,实现了如下功能: - 专业设置查看 - 学生管理 - 排课管理 - 教室管理 - 教师管理 - 班级管理 - 管理员登录 - 系办人员管理 - 课程情况查看 - 课程申请管理 - 课程设置管理 第2个角色为教师角色,实现了如下功能: - 学生管理 - 成绩录入管理 - 教师角色登录 - 申请增加课程 - 课程安排管理 第3个角色为学生角色,实现了如下功能: - 基本信息查看 - 学生角色登录 - 成绩查询 - 课程表查看 - 选课功能安排
第21章spring-mvc之缓存
华为网盘高级版
Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。
Java聊天室程序(java)
产品线经理转身赋能zzn.pptx
JAVA泡泡堂网络游戏的设计与实现
Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。
基于python深度度量学习准确预测蛋白质二级结构源码(期末大作业).zip已获导师指导并通过的97分的高分大作业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 基于python深度度量学习准确预测蛋白质二级结构源码(期末大作业).zip已获导师指导并通过的97分的高分大作业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 基于python深度度量学习准确预测蛋白质二级结构源码(期末大作业).zip已获导师指导并通过的97分的高分大作业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 基于python深度度量学习准确预测蛋白质二级结构源码(期末大作业).zip已获导师指导并通过的97分的高分大作业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 基于python深度度量学习准确预测蛋白质二级结构源码(期末大作业).zip已获导师指导并通过的97分的高分大作业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。
python: 【0514】基于Python省市区三级地址库.zip
该包包括6个完全装配和纹理的3D公民角色,以2种颜色变化的男性和女性角色为特色,45个带有卡通着色器的风格化道具。 完全装配+搅拌机形状 MIXAMO兼容 这些卡通风格的人形公民角色设计得非常吸引人和引人注目,非常适合用于游戏、动画和其他数字项目。 通过各种各样的姿势和表情,你可以很容易地与这些角色一起创造出观众一定会喜欢的动态场景。