java单例模式精解
Java单例模式详解

从这里再来总结单例模式的特点:首先,单例模式使类在程序生命周期的任何时刻都只有一个实例,然后,单例的构造函数是私有的,外部程序如果想要访问这个单例类的话,必须通过GetInstance()来请求(注意是请求)得到这个单例类的实例。
有的时候,总是容易把全局变量和单例模式给弄混了,下面就剖析一下全局变量和单例模式相比的缺点首先,全局变量呢就是对一个对象的静态引用,全局变量确实可以提供单例模式实现的全局访问这个功能,但是,它并不能保证您的应用程序中只有一个实例,同时,在编码规范中,也明确指出,应该要少用全局变量,因为过多的使用全局变量,会造成代码难读,还有就是全局变量并不能实现继承(虽然单例模式在继承上也不能很好的处理,但是还是可以实现继承的)而单例模式的话,其在类中保存了它的唯一实例,这个类,它可以保证只能创建一个实例,同时,它还提供了一个访问该唯一实例的全局访问点。
上面呢,差不多就将单例模式的核心给介绍完了,或许,您会觉得单例模式就这么个东西啊,不就是保证只有一个实例嘛,也太简单了,如果您真这么想的话,那您就错了,因为要保证在整个应用程序生命周期中保证只有一个实例不是那么容易的,下面就来看一种情况(这里先假设我的应用程序是多线程应用程序),同时还是以前面的Demo 来做为说明,如果在一开始调用GetInstance()时,是由两个线程同时调用的(这种情况是很常见的),注意是同时,(或者是一个线程进入if 判断语句后但还没有实例化Singleton 时,第二个线程到达,此时singleton 还是为null)这样的话,两个线程均会进入GetInstance(),而后由于是第一次调用GetInstance(),所以存储在Singleton 中的静态变量singleton 为null ,这样的话,就会让两个线程均通过if 语句的条件判断,然后调用new Singleton()了,public static Singleton GetInstance(){if (singleton == null){singleton = new Singleton();}return singleton;}这样的话,问题就出来了,因为有两个线程,所以会创建两个实例,很显然,这便违法了单例模式的初衷了,那么如何解决上面出现的这个问题(即多线程下使用单例模式时有可能会创建多个实例这一现象)呢?其实,这个是很好解决的,您可以这样思考这个问题:由于上面出现的问题中涉及到多个线程同时访问这个GetInstance(),那么您可以先将一个线程锁定,然后等这个线程完成以后,再让其他的线程访问GetInstance ()中的if 段语句,比如,有两个线程同时到达如果 singleton != null的话,那么上面提到的问题是不会存在的,因为已经存在这个实例了,这样的话,所有的线程都无法进入if 语句块,也就是所有的线程都无法调用语句new Singleton()了,这样还是可以保证应用程序生命周期中的实例只存在一个,但是如果此时的singleton == null的话,那么意味着这两个线程都是可以进入这个if 语句块的,那么就有可能出现上面出现的单例模式中有多个实例的问题,此时,我可以让一个线程先进入if 语句块,然后我在外面对这个if 语句块加锁,对第二个线程呢,由于if 语句进行了加锁处理,所以这个进程就无法进入if 语句块而处于阻塞状态,当进入了if 语句块的线程完成new Singleton()后,这个线程便会退出if 语句块,此时,第二个线程就从阻塞状态中恢复,即就可以访问if 语句块了,但是由于前面的那个线程已近创建了Singleton 的实例,所以singleton != null ,此时,第二个线程便无法通过if 语句的判断条件了,即无法进入if 语句块了,这样便保证了整个生命周期中只存在一个实例,也就是只有第一个线程创建了Singleton 实例,第二个线程则无法创建实例。
Java设计模式之《单例模式》及应用场景

Java设计模式之《单例模式》及应⽤场景所谓单例,指的就是单实例,有且仅有⼀个类实例,这个单例不应该由⼈来控制,⽽应该由代码来限制,强制单例。
单例有其独有的使⽤场景,⼀般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,⼀般都需要使⽤⼀个实例来进⾏记录,若多例计数则会不准确。
其实单例就是那些很明显的使⽤场合,没有之前学习的那些模式所使⽤的复杂场景,只要你需要使⽤单例,那你就使⽤单例,简单易理解。
所以我认为有关单例模式的重点不在于场景,⽽在于如何使⽤。
1、常见的单例模式有两种创建⽅式:所谓饿懒汉式与饿汉式(1)懒汉式 何为懒?顾名思义,就是不做事,这⾥也是同义,懒汉式就是不在系统加载时就创建类的单例,⽽是在第⼀次使⽤实例的时候再创建。
详见下⽅代码⽰例:public class LHanDanli {//定义⼀个私有类变量来存放单例,私有的⽬的是指外部⽆法直接获取这个变量,⽽要使⽤提供的公共⽅法来获取private static LHanDanli dl = null;//定义私有构造器,表⽰只在类内部使⽤,亦指单例的实例只能在单例类内部创建private LHanDanli(){}//定义⼀个公共的公开的⽅法来返回该类的实例,由于是懒汉式,需要在第⼀次使⽤时⽣成实例,所以为了线程安全,使⽤synchronized关键字来确保只会⽣成单例public static synchronized LHanDanli getInstance(){if(dl == null){dl = new LHanDanli();}return dl;}}(2)饿汉式 ⼜何为饿?饿者,饥不择⾷;但凡有⾷,必急⾷之。
此处同义:在加载类的时候就会创建类的单例,并保存在类中。
详见下⽅代码⽰例:public class EHanDanli {//此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中private static EHanDanli dl = new EHanDanli();//定义⽆参构造器,⽤于单例实例private EHanDanli(){}//定义公开⽅法,返回已创建的单例public static EHanDanli getInstance(){return dl;}}2、双重加锁机制 何为双重加锁机制? 在懒汉式实现单例模式的代码中,有使⽤synchronized关键字来同步获取实例,保证单例的唯⼀性,但是上⾯的代码在每⼀次执⾏时都要进⾏同步和判断,⽆疑会拖慢速度,使⽤双重加锁机制正好可以解决这个问题:public class SLHanDanli {private static volatile SLHanDanli dl = null;private SLHanDanli(){}public static SLHanDanli getInstance(){if(dl == null){synchronized (SLHanDanli.class) {if(dl == null){dl = new SLHanDanli();}}}return dl;}}看了上⾯的代码,有没有感觉很⽆语,双重加锁难道不是需要两个synchronized进⾏加锁的吗? ...... 其实不然,这⾥的双重指的的双重判断,⽽加锁单指那个synchronized,为什么要进⾏双重判断,其实很简单,第⼀重判断,如果单例已经存在,那么就不再需要进⾏同步操作,⽽是直接返回这个实例,如果没有创建,才会进⼊同步块,同步块的⽬的与之前相同,⽬的是为了防⽌有两个调⽤同时进⾏时,导致⽣成多个实例,有了同步块,每次只能有⼀个线程调⽤能访问同步块内容,当第⼀个抢到锁的调⽤获取了实例之后,这个实例就会被创建,之后的所有调⽤都不会进⼊同步块,直接在第⼀重判断就返回了单例。
JAVA常用设计模式详解大全

JAVA常用设计模式详解大全设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
它是将设计经验系统化的产物,目的是提高代码的可复用性、可维护性和可扩展性。
常用的设计模式主要分为三类:创建型模式、结构型模式和行为型模式。
下面将详细介绍每一种模式及其使用方式。
一、创建型模式1. 单例模式(Singleton Pattern)单例模式用于确保一个类只有一个实例,并提供全局访问方法。
常用于线程池、缓存和日志等场景。
2. 工厂模式(Factory Pattern)工厂模式用于根据不同的输入参数创建不同的实例。
常用于对象的创建过程复杂或者需要隐藏创建逻辑的场景。
3. 抽象工厂模式(Abstract Factory Pattern)抽象工厂模式用于创建一系列相关或依赖的对象,且客户端无需关心具体的实现类。
常用于创建多个产品族的场景。
4. 建造者模式(Builder Pattern)建造者模式用于将一个复杂对象的创建过程和其表示分离,以使同样的创建过程可以创建不同的表示。
常用于构建参数较多的对象。
5. 原型模式(Prototype Pattern)原型模式用于创建对象的克隆,避免了通过new关键字创建对象的性能开销。
常用于创建对象的过程耗费资源较多的场景。
二、结构型模式1. 适配器模式(Adapter Pattern)适配器模式用于将一个类的接口转换为客户端所期望的接口。
常用于不兼容接口之间的适配。
2. 装饰器模式(Decorator Pattern)装饰器模式用于动态地给一个对象添加额外的功能。
常用于对原有类的功能进行扩展或包装。
3. 代理模式(Proxy Pattern)代理模式用于控制对其他对象的访问。
常用于远程代理、虚拟代理、保护代理等场景。
4. 外观模式(Facade Pattern)外观模式用于提供一个简化的接口,隐藏一系列复杂的子系统。
常用于简化复杂系统的接口调用过程。
浅析Java单例设计模式(自写demo)

浅析Java单例设计模式(⾃写demo)⽬录单例模式特点单例模式优点实现⽅式饿汉式(线程安全)懒汉式单例模式特点1、构造器私有2、在⼀个Java应⽤程序中,可保证只有⼀个实例对象3、只提供⼀个供外界调⽤的getInstance()⽅法单例模式优点1、减少某些对象的频繁创建,降低系统开销和内存占⽤2、外部调⽤不使⽤new关键字,降低系统内存的使⽤频率3、对于特殊的类,在系统中只能存在⼀个实例,否则系统⽆法正常运⾏,⽐如Controller实现⽅式这⾥简单介绍两种实现⽅式饿汉式(线程安全)/*** @author: xuzhilei6656* @create: 2021-12-12 12:07* @description: 单例模式(饿汉式)**/public class Singleton {//创建实例private static Singleton instance = new Singleton();//私有构造器private Singleton(){}//获取实例的静态⽅法public static Singleton getInstance(){return instance;}}实例对象在类被加载的时候就已经完成初始化,外界调⽤拿到的都是这个唯⼀的实例对象懒汉式/*** @author: xuzhilei6656* @create: 2021-12-12 12:22* @description: 单例模式(懒汉式)**/public class Singleton {//声明⼀个变量private static Singleton instance;//私有构造器private Singleton(){}//获取实例的静态⽅法public static Singleton getInstance(){//如果是⾸次调⽤,实例对象还没有被创建,就需要创建,否则都是返回已经创建过的那个对象if (instance == null){instance = new Singleton();}return instance;}}对⽐饿汉式可见,实例对象在类被加载的时候并没有进⾏创建,在⾸次调⽤的时候才被创建,以后再被调⽤,返回的也是那个唯⼀的实例对象。
Java设计模式之(一)------单例模式

Java设计模式之(⼀)------单例模式1、什么是单例模式 采取⼀定的办法保证在整个软件系统中,确保对于某个类只能存在⼀个实例。
单例模式有如下三个特点: ①、单例类只能有⼀个实例 ②、单例类必须⾃⼰创建⾃⼰的实例 ③、单例类必须提供外界获取这个实例的⽅法2、单例类的设计思想(Singleton) ①、外界不能创建这个类的实例,那么必须将构造器私有化。
public class Singleton {//构造器私有化private Singleton(){}} ②、单例类必须⾃⼰创建⾃⼰的实例,不能允许在类的外部修改内部创建的实例。
⽐如将这个实例⽤ private 声明。
为了外界能访问到这个实例,我们还必须提供 get ⽅法得到这个实例。
因为外界不能 new 这个类,所以我们必须⽤ static 来修饰字段和⽅法。
//在类的内部⾃⼰创建实例private static Singleton singleton = new Singleton();//提供get ⽅法以供外界获取单例public Singleton getInstance(){ return singleton;} ③、是否⽀持延迟加载? 有些情况下,创建某个实例耗时长,占⽤资源多,⽤的时候也少,我们会考虑在⽤到的时候才会去创建,这就是延迟加载。
但有些情况,按照 fail-fast 的设计原则(有问题及早暴露),⽐如某个实例占⽤资源很多,如果延迟加载,会在程序运⾏⼀段时间后OOM,如果在程序启动的时候就创建这个实例,我们就可以⽴即去修复,不会导致程序运⾏之后的系统奔溃。
所以,是否⽀持延迟加载需要结合实际情况考虑。
④、保证线程安全 这个是⼀定要考虑的,如果你写的单例类存在线程安全问题,那就是伪单例了。
3、单例类的⼏种实现⽅式3.1 单例模式之饿汉模式public class Singleton {//构造器私有化private Singleton(){}//在类的内部⾃⼰创建实例private static Singleton singleton = new Singleton();//提供get ⽅法以供外界获取单例public static Singleton getInstance(){return singleton;}} 测试:public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1.equals(s2)); //true} 这种模式在类加载的时候实例 singleton 就已经创建并初始化好了,所以是线程安全的。
Java中的单例模式详解(完整篇)

Java中的单例模式详解(完整篇)⽬录前⾔WHATWHY饿汉式实现⼀:静态实例参数与静态代码块实现⼆:静态内部类懒汉式错误⼀:单线程实现错误⼆:同步⽅法错误三:同步代码块之单次检查错误四:同步代码块之双重检查正确:双重检查+阻⽌重排序枚举场景总结前⾔个⼈认为单例模式是设计模式中最简单也是最常⽤的⼀种,是对有限资源合理利⽤的⼀种⽅式。
这个模式看似简单,但是其中蕴含了关于并发、类加载、序列化等⼀系列深层次的知识,如果理解不够深,就有可能在⾼并发时遇到难以预期的异常,或者会造成资源浪费。
所以本⽂会从将⽬前Java领域最常⽤的⼏种单例模式列出来,供⼤家参考。
WHAT维基百科给出了解释、实现的思路以及应该注意的地⽅:单例模式,也叫单⼦模式,是⼀种常⽤的软件设计模式,属于创建型模式的⼀种。
在应⽤这个模式时,单例对象的类必须保证只有⼀个实例存在。
实现单例模式的思路是:⼀个类能返回对象⼀个引⽤(永远是同⼀个)和⼀个获得该实例的⽅法(必须是静态⽅法,通常使⽤getInstance这个名称);当我们调⽤这个⽅法时,如果类持有的引⽤不为空就返回这个引⽤,如果类保持的引⽤为空就创建该类的实例并将实例的引⽤赋予该类保持的引⽤;同时我们还将该类的构造函数定义为私有⽅法,这样其他处的代码就⽆法通过调⽤该类的构造函数来实例化该类的对象,只有通过该类提供的静态⽅法来得到该类的唯⼀实例。
单例模式在多线程的应⽤场合下必须⼩⼼使⽤。
如果当唯⼀实例尚未创建时,有两个线程同时调⽤创建⽅法,那么它们同时没有检测到唯⼀实例的存在,从⽽同时各⾃创建了⼀个实例,这样就有两个实例被构造出来,从⽽违反了单例模式中实例唯⼀的原则。
解决这个问题的办法是为指⽰类是否已经实例化的变量提供⼀个互斥锁(虽然这样会降低效率)。
类图是:WHY正如定义所说,单例模式就是整个内存模型中,只有⼀个实例。
实例少了,内存占⽤就少。
同时,只有⼀个实例,也就只需要构建⼀个对象,计算就少。
深入理解java设计模式之单例模式

深⼊理解java设计模式之单例模式今天看了⼀天的设计模式。
单例模式:常⽤的分为两种懒汉模式和饿汉模式单例模式:1、单例类确保⾃⼰只有⼀个实例。
2、单例类必须⾃⼰创建⾃⼰的实例。
3、单例类必须为其他对象提供唯⼀的实例。
举个例⼦,⽹站的计数器就是单例模式的⼀个体现。
因为总不能打开⼀次⽹址就去new⼀个新的计数器对象。
⽽是⼤家都去⽤⼀个计数器下⾯来点⼲货数据库的连接池⼀般也是使⽤单例模式去实现的。
单例模式常⽤的分为两种懒汉模式://懒汉式单例类.在第⼀次调⽤的时候实例化⾃⼰public class Singleton {private Singleton() {}private static Singleton single=null;//静态⼯⼚⽅法public static Singleton getInstance() {if (single == null) {single = new Singleton();}return single;}}public class Singleton {private static class LazyHolder {private static final Singleton INSTANCE = new Singleton(); //使⽤静态内部类的⽅式实现线程安全,这个没研究。
}private Singleton (){}public static final Singleton getInstance() {return LazyHolder.INSTANCE;}}考虑到线程可能并发的可能,所以懒汉模式是线程不安全的。
然⽽我们可以去使⽤ synchronized 这个⽅法让所有的访问去同步,从⽽实现线程安全但是在⽹上看java有反射机制,通过Java反射机制是能够实例化构造⽅法为private的类的,这⾥姑且先不考虑反射机制。
但是我们的知道!饿汉模式://饿汉式单例类.在类初始化时,已经⾃⾏实例化public class Singleton1 {private Singleton1() {}private static final Singleton1 single = new Singleton1();//静态⼯⼚⽅法public static Singleton1 getInstance() {return single;}}饿汉式在类创建的同时就已经创建好⼀个静态的对象供系统使⽤,以后不再改变,所以天⽣是线程安全的。
java枚举单例模式解析

java枚举单例模式解析Java中的枚举单例模式是一种非常安全且简单的单例模式实现方式。
它利用枚举类型的特性,保证了在任何情况下都只会有一个实例被创建,并且可以避免反射和序列化等问题。
在枚举类型中,每个枚举常量都是枚举类型的实例。
因此,通过定义枚举常量来实现单例模式,可以保证只有一个实例对象被创建。
同时,枚举类型在加载时会被初始化,因此可以避免线程安全问题。
下面是一个使用枚举实现单例模式的例子:```javapublic enum Singleton {INSTANCE;public void showMessage() {System.out.println('Hello World!');}}```在上面的例子中,我们定义了一个枚举类型Singleton,其中只有一个枚举常量INSTANCE。
通过定义INSTANCE枚举常量来实现单例模式,可以保证只有一个实例对象被创建。
我们可以通过以下方式来调用Singleton实例对象的方法:```javaSingleton.INSTANCE.showMessage();```在使用枚举单例模式时,需要注意以下几点:1. 枚举类型不支持继承,因此无法通过继承来扩展单例的功能。
2. 枚举类型的构造函数只会被调用一次,在枚举常量被加载时进行初始化。
3. 枚举常量可以实现接口,在枚举类型中实现接口的方法可以被所有枚举常量共享。
总之,使用枚举单例模式可以保证线程安全和单例对象的唯一性,并且可以避免反射和序列化等问题。
因此,在需要实现单例模式时,可以考虑使用枚举单例模式。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Singleton模式可以是很简单的,它的全部只需要一个类就可以完成(看看这章可怜的UML图)。
但是如果在“对象创建的次数以及何时被创建”这两点上较真起来,Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论、涉及到多个类加载器(ClassLoader)协同时、涉及到跨JVM(集群、远程EJB等)时、涉及到单例对象被销毁后重建等。
对于复杂的情况,本章中会涉及到其中一些[1]目的:希望对象只创建一个实例,并且提供一个全局的访问点。
场景:Kerrigan对于Zerg来说是个至关重要的灵魂人物,无数的Drone、Zergling、Hydralisk……可以被创造、被牺牲,但是Kerrigan得存在关系到Zerg在这局游戏中的生存,而且Kerrigan是不允许被多次创造的,必须有且只有一个虫族刀锋女王的实例存在,这不是游戏规则,但这是个政治问题。
分析:如前面一样,我们还是尝试使用代码来描述访问Kerrigan的过程,看看下面的UML图,简单得我都不怎么好意思放上来占版面。
图6.1 单例模式的UML图结构是简单的,只是我们还有一些小小的要求如下:1.最基本要求:每次从getInstance()都能返回一个且唯一的一个Kerrigan 对象。
2.稍微高一点的要求:Kerrigan很忙,很多人找,所以希望这个方法能适应多线程并发访问。
3.再提高一点的要求:Zerg是讲究公务员效率的社会,希望找Kerrigan的方法性能尽可能高。
4.最后一点要求是Kerrigan自己提出的:体谅到Kerrigan太累,希望多些睡觉时间,因此Kerrigan希望实现懒加载(Lazy Load),在需要的时候才被构造。
5.原本打算说还提要处理多ClassLoader、多JVM等情况,不过还是不要把情况考虑的太复杂了,暂且先放过作者吧(-_-#)。
我们第一次写的单例模式是下面这个样子的:Java代码1./**2. * 实现单例访问Kerrigan的第一次尝试3. */4.public class SingletonKerriganA {5.6. /**7. * 单例对象实例8. */9. private static SingletonKerriganA instance = null;10.11. public static SingletonKerriganA getInstance() {12. if (instance == null) { //line A13. instance = new SingletonKerriganA(); //line B14. }15. return instance;16. }17.}这个写法我们把四点需求从上往下检测,发现第二点的时候就出了问题,假设这样的场景:两个线程并发调用SingletonKerriganA.getInstance(),假设线程一先判断完instance是否为null,既代码中的line A进入到line B的位置。
刚刚判断完毕后,JVM将CPU资源切换给线程二,由于线程一还没执行line B,所以instance仍然是空的,因此线程二执行了new SignletonKerriganA()操作。
片刻之后,线程一被重新唤醒,它执行的仍然是new SignletonKerriganA()操作,好了,问题来了,两个Kerrigan谁是李逵谁是李鬼?紧接着,我们做单例模式的第二次尝试:Java代码1./**2. * 实现单例访问Kerrigan的第二次尝试3. */4.public class SingletonKerriganB {5.6. /**7. * 单例对象实例8. */9. private static SingletonKerriganB instance = null;10.11. public synchronized static SingletonKerriganB getInstance(){12. if (instance == null) {13. instance = new SingletonKerriganB();14. }15. return instance;16. }17.}比起第一段代码仅仅在方法中多了一个synchronized修饰符,现在可以保证不会出线程问题了。
但是这里有个很大(至少耗时比例上很大)的性能问题。
除了第一次调用时是执行了SingletonKerriganB的构造函数之外,以后的每一次调用都是直接返回instance对象。
返回对象这个操作耗时是很小的,绝大部分的耗时都用在synchronized修饰符的同步准备上,因此从性能上说很不划算。
那继续把代码改成下面的样子:Java代码1./**2. * 实现单例访问Kerrigan的第三次尝试3. */4.public class SingletonKerriganC {5.6. /**7. * 单例对象实例8. */9. private static SingletonKerriganC instance = null;10.11. public static SingletonKerriganC getInstance() {12. synchronized (SingletonKerriganC.class) {13. if (instance == null) {14. instance = new SingletonKerriganC();15. }16. }17. return instance;18. }19.}基本上,把synchronized移动到代码内部是没有什么意义的,每次调用getInstance()还是要进行同步。
同步本身没有问题,但是我们只希望在第一次创建Kerrigan实例的时候进行同步,因此我们有了下面的写法——双重锁定检查(DCL)。
Java代码1./**2. * 实现单例访问Kerrigan的第四次尝试3. */4.public class SingletonKerriganD {5.6. /**7. * 单例对象实例8. */9. private static SingletonKerriganD instance = null;10.11. public static SingletonKerriganD getInstance() {12. if (instance == null) {13. synchronized (SingletonKerriganD.class) {14. if (instance == null) {15. instance = new SingletonKerriganD();16. }17. }18. }19. return instance;20. }21.}看起来这样已经达到了我们的要求,除了第一次创建对象之外,其他的访问在第一个if中就返回了,因此不会走到同步块中。
已经完美了吗?我们来看看这个场景:假设线程一执行到instance = new SingletonKerriganD()这句,这里看起来是一句话,但实际上它并不是一个原子操作(原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。
事实上高级语言里面非原子操作有很多,我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大致做了3件事情:1.给Kerrigan的实例分配内存。
2.初始化Kerrigan的构造器3.将instance对象指向分配的内存空间(注意到这步instance就非null 了)。
但是,由于Java编译器允许处理器乱序执行(out-of-order),以及JDK1.5之前JMM(Java Memory Medel)中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是无法保证的,也就是说,执行顺序可能是1-2-3也可能是1-3-2,如果是后者,并且在3执行完毕、2未执行之前,被切换到线程二上,这时候instance因为已经在线程一内执行过了第三点,instance已经是非空了,所以线程二直接拿走instance,然后使用,然后顺理成章地报错,而且这种难以跟踪难以重现的错误估计调试上一星期都未必能找得出来,真是一茶几的杯具啊。
DCL的写法来实现单例是很多技术书、教科书(包括基于JDK1.4以前版本的书籍)上推荐的写法,实际上是不完全正确的。
的确在一些语言(譬如C语言)上DCL是可行的,取决于是否能保证2、3步的顺序。
在JDK1.5之后,官方已经注意到这种问题,因此调整了JMM、具体化了volatile关键字,因此如果JDK 是1.5或之后的版本,只需要将instance的定义改成“private volatile static SingletonKerriganD instance = null;”就可以保证每次都去instance都从主内存读取,就可以使用DCL的写法来完成单例模式。
当然volatile或多或少也会影响到性能,最重要的是我们还要考虑JDK1.42以及之前的版本,所以本文中单例模式写法的改进还在继续。
代码倒越来越复杂了,现在先来个返璞归真,根据JLS(Java Language Specification)中的规定,一个类在一个ClassLoader中只会被初始化一次,这点是JVM本身保证的,那就把初始化实例的事情扔给JVM好了,代码被改成这样:Java代码1./**2. * 实现单例访问Kerrigan的第五次尝试3. */4.public class SingletonKerriganE {5.6. /**7. * 单例对象实例8. */9. private static SingletonKerriganE instance = new SingletonKerriganE();10.11. public static SingletonKerriganE getInstance() {12. return instance;13. }14.}好吧,如果这种写法是完美的话,那前面那么几大段话就是作者在消遣各位读者。
这种写法不会出现并发问题,但是它是饿汉式的,在ClassLoader加载类后Kerrigan的实例就会第一时间被创建,饿汉式的创建方式在一些场景中将无法使用:譬如Kerrigan实例的创建是依赖参数或者配置文件的,在getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。