原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6510196.html

  所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。

  单例有其独有的使用场景,一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,一般都需要使用一个实例来进行记录,若多例计数则会不准确。

  其实单例就是那些很明显的使用场合,没有之前学习的那些模式所使用的复杂场景,只要你需要使用单例,那你就使用单例,简单易理解。

  所以我认为有关单例模式的重点不在于场景,而在于如何使用。

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,为什么要进行双重判断,其实很简单,第一重判断,如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例。至于第二个判断,个人感觉有点查遗补漏的意味在内(期待高人高见)。

  补充:关于锁内部的第二重空判断的作用,当多个线程一起到达锁位置时,进行锁竞争,其中一个线程获取锁,如果是第一次进入则dl为null,会进行单例对象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返回已创建的单例对象。

  不论如何,使用了双重加锁机制后,程序的执行速度有了显著提升,不必每次都同步加锁。

  其实我最在意的是volatile的使用,volatile关键字的含义是:被其所修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存来实现,从而确保多个线程能正确的处理该变量。该关键字可能会屏蔽掉虚拟机中的一些代码优化,所以其运行效率可能不是很高,所以,一般情况下,并不建议使用双重加锁机制,酌情使用才是正理!

  更进一步说,其实使用volatile的目的是为了防止暴露一个未初始化的不完整单例实例,导致系统崩溃。因为创建单例实例其实需要经过以下几步:首先分配内存空间、然后将内存空间的首地址指向引用(指针),最后调用构造器创建实例,由于在第二步的时候这个引用(指针)就会变的非null,那么在第三步未执行,真正的单例实例还未创建完成的时候,一个线程过来在第一个校验中为false,将会直接将不完整的实例返回,从而造成系统崩溃。

3、类级内部类方式

  饿汉式会占用较多的空间,因为其在类加载时就会完成实例化,而懒汉式又存在执行速率慢的情况,双重加锁机制呢?又有执行效率差的毛病,有没有一种完美的方式可以规避这些毛病呢?

  貌似有的,就是使用类级内部类结合多线程默认同步锁,同时实现延迟加载和线程安全。

 public class ClassInnerClassDanli {
public static class DanliHolder{
private static ClassInnerClassDanli dl = new ClassInnerClassDanli();
}
private ClassInnerClassDanli(){}
public static ClassInnerClassDanli getInstance(){
return DanliHolder.dl;
}
}

  如上代码,所谓类级内部类,就是静态内部类,这种内部类与其外部类之间并没有从属关系,加载外部类的时候,并不会同时加载其静态内部类,只有在发生调用的时候才会进行加载,加载的时候就会创建单例实例并返回,有效实现了懒加载(延迟加载),至于同步问题,我们采用和饿汉式同样的静态初始化器的方式,借助JVM来实现线程安全。

  其实使用静态初始化器的方式会在类加载时创建类的实例,但是我们将实例的创建显式放置在静态内部类中,它会导致在外部类加载时不进行实例创建,这样就能实现我们的双重目的:延迟加载和线程安全。

4、使用

  在Spring中创建的Bean实例默认都是单例模式存在的。


同系列文章:

Java设计模式之《单例模式》及应用场景的更多相关文章

  1. java设计模式之组合模式

    组合模式 组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性.掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 & ...

  2. Java设计模式应用——组合模式

    组合模式实际上是一种树形数据结构.以windows目录系统举例,怎么样用java语言描述一个文件夹? 定义一个文件夹类,文件夹类中包含若干个子文件类和若干个文件类. 进一步抽象,把文件夹和文件都看做节 ...

  3. java设计模式5.组合模式、门面模式、享元模式、桥接模式

    组合模式 在面向对象的语言中,树结构有着巨大的威力,一个基于继承的类型的等级结构便是一个数结构,一个基于合成的对象结构也是一个数结构.组合模式将部分与整体的关系用树结构表示出来,使得客户端把一个个单独 ...

  4. JAVA设计模式之组合模式(composite)

    组合模式:树状结构专用模式 代码如下: package com.srr.dp.composite; import java.util.ArrayList; import java.util.List; ...

  5. 老和尚给小和尚讲故事引发了Java设计模式:组合模式

    目录 示例 组合模式 定义 意图 主要解决问题 优缺点 安全式和透明式的组合模式 安全式的合成模式的结构 透明式的合成模式的结构 老和尚和小和尚的故事 示例 有一个绘图系统,可以描绘各种图形,假设现在 ...

  6. Java设计模式之模板模式及使用场景

    模板模式,顾名思义,就是通过模板拓印的方式. 定义模板,就是定义框架.结构.原型.定义一个我们共同遵守的约定. 定义了模板,我们的剩余工作就是对其进行充实.丰润,完善它的不足之处. 定义模板采用抽象类 ...

  7. Java设计模式之代理模式(静态代理和JDK、CGLib动态代理)以及应用场景

    我做了个例子 ,需要可以下载源码:代理模式 1.前言: Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没 ...

  8. Java设计模式——装饰者模式

    JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...

  9. JAVA设计模式--装饰器模式

    装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...

  10. 折腾Java设计模式之访问者模式

    博客原文地址:折腾Java设计模式之访问者模式 访问者模式 Represent an operation to be performed on the elements of an object st ...

随机推荐

  1. php 常用代码段

    1.写文件 $fp = fopen("jsapi_ticket.json", "w+"); fwrite($fp, $str); fclose($fp); 2. ...

  2. Java经典案例之-“成绩等级划分”

    /** * 描述:在对话框中输入学习成绩,利用条件运算符的嵌套来完成此题:学习成绩>=90分的同学用A表示,60-89分之间的用B表示, * 60分以下的用C表示. * 分析:可以利用三目运算符 ...

  3. --@angularJS--浅谈class与Ng-Class的应用

    1.angularjs的表达式是可以写在class里面动态改变其值的,写法如下: □.CSS1.html: <!doctype html><html ng-app="MyC ...

  4. 第五组UI组件:ProgressBar及其子类

    ProgressBar组件也是一组重要的组件,ProgressBar本身代表了进度条组件,它还派生了两个常用的组件:SeekBar和RatingBar.ProgressBar及其子类在用上十分相似,只 ...

  5. easyUI resizable组件使用

    easyUI resizable组件使用: <!DOCTYPE html> <html lang="en"> <head> <meta c ...

  6. 马丁 福勒 Martin Fowler 关于依赖注入和反转控制的区别

    马丁 福勒 Martin Fowler 关于依赖注入和反转控制的区别 http://martinfowler.com/articles/injection.html 中文翻译:http://files ...

  7. 树莓派VNC搭建相关问题,啦啦啦~

    为了节省money,于是我决定用VNC界面来代替显示器,为后面做C++ Qt以及Python Qt开发打下基础,我别无选择!下面开始进入正题: 1-- 下载VNC-Viewer-6.0.1-Windo ...

  8. ArcGIS制图表达Representation-符号制作

    ArcGIS制图表达Representation-符号制作 by 李远祥 在ArcGIS的符号里面,存在着两种符号体系,一种是传统的标准符号体系,一种是制图表达符号体系.标准符号几乎被绝大部分ArcG ...

  9. 关于自定义的 XIB cell上的 button如何在控制器里实现点击方法

    直接调用cell.button addTarget 的方法点击事件是失效的 这时需要你在xib中设置button的tag值 然后在返回cell的时候添加点击事件 UIButton *button = ...

  10. 深入子元素的width与父元素的width关系

    深入理解父元素与子元素的width关系 对于这一部分内容,如果理解准确,可以更容易控制布局,节省不必要的代码,这里将简单研究. 第一部分:父子元素都是内联元素 代码演示如下: <!DOCTYPE ...