概述

  提到单例模式,我们并不陌生,而且面试中也常常会问单例模式相关的问题,接下来就谈谈单例模式,这篇文章会回答如下几个问题:

  1. 什么是单例模式?
  2. 单例模式有几种实现方式,以及各种实现方式的优劣?
  3. 单例模式有什么用?

什么是单例模式

  保证一个类仅有一个实例,并提供一个访问它的全局访问点。这是《设计模式》(艾迪生维斯理, 1994)中的定义。

单例模式有几种实现方式

  先说答案,有三种,基本上我们只知道两种,就是懒汉式和饿汉式,其实还有第三种,通过静态内部类实现单例模式,下面就说说这三种实现方式。

饿汉式

  

package com.gxl.demo.DesignPattern.singletonpattern;

/**
* Description: 饿汉式单例模式
*/
public class EagerSingleton {
//为什么EagerSingleton要是static呢?因为声明成static就是当类被加载的时候就会执行new EagerSingleton(),
// 无论getInstance方法有没有被调用,这也是饿汉式这个名称由来,就是说,无论你用不用,我都会实例化。为什么是final类型呢?
// 因为声明成final类型就是当类被加载实例化之后,
// 这个instance的值就不允许修改了,就是说只能被实例化一次
private final static EagerSingleton instance = new EagerSingleton();
//私有化构造方法,不允许外部应用通过new来创建对象
private EagerSingleton(){}
//静态方法,提供给外部应用获取单例对象
public static EagerSingleton getInstance(){
return instance;
}
} class Test{
public static void main(String[] args) {
EagerSingleton eagerSingleton1 = EagerSingleton.getInstance();
EagerSingleton eagerSingleton2 = EagerSingleton.getInstance();
if (eagerSingleton1 == eagerSingleton2){
System.out.println("是单例模式,保证了单例对象的唯一性");
}
}
}

饿汉式总结

饿汉式在没有被使用的情况下就把单例对象创建出来,占用内存,并不是一种很好的方式。

懒汉式

/**
* Description: 懒汉式单例模式
*/
public class LazySingleton { private static LazySingleton instance = null;
//私有防止外部应用new新的实例
private LazySingleton(){}
//双重检验锁定,先判断instance是否为null而不是先锁定的好处是如果单例已经创建就不用在锁定
//提升效率,锁定之后再次判断instance是否为null的目的是为了防止多个线程同时通过了第一个判断
//然后创建多个对象
public static LazySingleton getInstance(){
if (instance == null){
synchronized (LazySingleton.class){
if (instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}

懒汉式总结

  为什么叫做懒汉式呢?因为只有当调用getInstance方法才会实例化,这就是一种懒加载。懒汉式解决了饿汉式占用资源的问题,但是又有一个新问题,就是使用synchronized效率低下。那有没有一种完美的方式,既不占用内存,效率又很高的方式呢?就是下面要介绍的。

IoDH(Initialization Demand Holder (IoDH))

package com.gxl.demo.DesignPattern.singletonpattern;

/**
* Description:IoDH
*/
public class IoDHSingleton {
//私有化构造方法,防止外部应用new新实例
private IoDHSingleton(){}
//不会初始化内部类,如果这个不是内部类就会被初始化,因为类中存在静态变量,并且静态变量被初始化赋值
private static class HodlerClass{
private final static IoDHSingleton instance = new IoDHSingleton();
}
//只有这个方法被调用,上面的静态内部类才会被初始化
public static IoDHSingleton getInstance(){
return HodlerClass.instance;
} public static void main(String[] args) {
IoDHSingleton ioDHSingleton1 = IoDHSingleton.getInstance();
IoDHSingleton ioDHSingleton2 = IoDHSingleton.getInstance();
if (ioDHSingleton1 == ioDHSingleton2){
System.out.println("是单例模式");
}
}
}

IoDH总结

其实上面的代码有两个疑问,上面我说过,IoDH完美的解决了延迟加载,而且不需要使用锁,第一个问题,为什么通过静态内部类可以实现延迟加载?第二个问题,不加锁如何保证线程安全?其实第二个问题在饿汉式中也存在这个问题,那下面来回答这两个问题。

第一个问题,为什么通过静态内部类可以实现延迟加载?

要解决第一个问题,需要先明白类什么时候会被加载。

注:以下内容参考这篇博客:深入理解单例模式:静态内部类单例原理

1.遇到new、getstatic、setstatic或者invokestatic这4个字节码指令时,对应的java代码场景为:new一个关键字或者一个实例化对象时、读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、调用一个类的静态方法时。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。
3.当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
5.当使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
这5种情况被称为是类的主动引用,注意,这里《虚拟机规范》中使用的限定词是"有且仅有",那么,除此之外的所有引用类都不会对类进行初始化,称为被动引用。静态内部类就属于被动引用的行列。

可以看出,静态内部类不属于上面5条的任意一条,所以不会被初始化,这里要区分一下类的初始化和类的实例化:

类的实例化是指创建一个类的实例(对象)的过程;
类的初始化是指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。

第二个问题,不加锁如何保证线程安全?

要回答这个问题,需要明白另一个问题,就是类初始化时,jvm是否可以保证其本身是线程安全的?

  这是答案,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。

故而,可以看出instance在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

java基础之----23种设计模式(单例模式)的更多相关文章

  1. Java编程的23种设计模式

    设计模式(Design Patterns)                                   --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用. ...

  2. JAVA开发的23种设计模式之 --- 桥接模式

    桥接模式 概述:将抽象部分与他的实现部分分离,这样抽象化与实现化解耦,使他们可以独立的变化.如何实现解耦的呢,就是通过提供抽象化和实现化之间的桥接结构.    应用场景        实现系统可能有多 ...

  3. JAVA中的23种设计模式

    http://blog.csdn.net/chmask/article/details/2631485 http://www.cnblogs.com/hnrainll/archive/2011/12/ ...

  4. 浅析Java中的23种设计模式

    前言 设计模式不论是在我们学习编程,还是在工作和面试过程中,都会涉及到的一个问题,所以了解和学习好设计模式,是我们每一位码农必须要具备的技能,对以后的发展和自己技能的提升都有好处. 什么是设计模式(D ...

  5. 23种设计模式--单例模式-Singleton

    一.单例模式的介绍 单例模式简单说就是掌握系统的至高点,在程序中只实例化一次,这样就是单例模式,在系统比如说你是该系统的登录的第多少人,还有数据库的连接池等地方会使用,单例模式是最简单,最常用的模式之 ...

  6. GOF 23种设计模式-单例模式

    • 创建型模式: – 单例模式.工厂模式.抽象工厂模式.建造者模式.原型模式. • 结构型模式: – 适配器模式.桥接模式.装饰模式.组合模式.外观模式.享元模式.代理模 式. • 行为型模式: – ...

  7. Java中的23种设计模式与7大原则

    一.创建型模式 1.抽象工厂模式(Abstract factory pattern): 提供一个接口, 用于创建相关或依赖对象的家族, 而不需要指定具体类.2.生成器模式(Builder patter ...

  8. java实现的23种设计模式 (个人推荐)

    http://zz563143188.iteye.com/blog/1847029 mark下,个人用,大家会也可以看看写的不错.

  9. Java基础-Java中23种设计模式之常用的设计模式

    Java基础-Java中23种设计模式之常用的设计模式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   一.设计模式分类 设计模式是针对特定场景给出的专家级的解决方案.总的来说设 ...

随机推荐

  1. 冬日曙光——回溯CNN的诞生

    前言 卷积神经网络(CNN)作为深度学习的重要一支,在当前计算机视觉领域应用相当广泛.本文回顾了深度学习的发展历程,讲述CNN基本的理论概念和第一代卷积神经网络LeNet-5的建立.文章言有不当之处, ...

  2. learn more ,study less(二):整体性学习技术(上)

    前言:在学习整体性学习概念时,一个很好的方法是把它比喻成下棋,首先你要了解下棋的 基本规则和基本目标,本书第一部分可以看做是介绍关于整体性学习的一整套规则和目标. 一旦你理解了下棋的基本规则,你就要开 ...

  3. Servlet概念及配置

    Servlet 简介: servlet就是sun公司开发动态web的一门技术 Sun在这些API中提供一个接口叫做:Servlet,如果逆向开发一个Servlet程序,只需要完成两个小步骤: 1.编写 ...

  4. MYSQL-innobackupex备份脚本

    自动化运维,是数据库管理员的不懈追求.目前出现了一些数据库自动管理平台,但出现时间较短,不够稳定.最常用的工具依然是shell脚本. 现在需要管理的某mysql数据库,数据量在800G,使用dump备 ...

  5. Nutz-使用Jspview跳转页面报404

    案例 今天在前段页面提交登陆请求时,后台报404,找不到对应的页面,但是该jsp已经放在了正确的目录下,并且请求地址也没错,就是返回对应jsp的时候找不到该文件 解决方案 经排查,原来犯了个低级错误, ...

  6. js的new操作符深度解析

    引言 我们都知道new操作符在js中一般是用来创建一个构造函数的实例,它在创建实例具体做了什么,MDN文档是这么说的: 我一开始看到,完全没有任何的头绪和理解,到底什么意思,后面通过上网查阅了大量的资 ...

  7. struts.xml头部代码

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "- ...

  8. POJ_1564_dfs

    题目描述: 每组数据给定一个大的数,和一系列降序的数值,要求列出不重复的数值中挑选的数的和为大数的方案,每一种方案里,每个数值最多只能使用一次. 思路: dfs基础题,每次记录大数和当前总和的差值,当 ...

  9. Go语言实现:【剑指offer】二叉树中和为某一值的路径

    该题目来源于牛客网<剑指offer>专题. 输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路 ...

  10. C和C++中字符串与数的转换

    以下内容来自<C/C++程序设计实用案例教程> 1.在C语言中的转换方式 1.1数字转换为字符串 在C语言中,sprintf函数可以将任何多个数字格式化为指定格式的字符串,sprintf函 ...