一、介绍

有时,我们需要某个类的实例始终只有一个,举个例子,如果用面向对象语言写的操作系统,那么桌面这个实例肯定就只有一个,无论从哪个地方进入的桌面,都是同一个。

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类
只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session
对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个
SessionFactory就够,这是就会使用到单例模式。

用到地方

  • Servlet是单例的
  • Spring中的bean默认是单例的

二、常用方式

需求存在,实现方式有多种,单例模式就有很多种实现方式,我自己以前了解的时候只知道懒汉式和饿汉式。但是后来发现有八种之多,虽然分为八种有点刻意分化,有点勉强,但是在学习的时候,也无可厚非。八种是具体细分,大体来说:饿汉式两种,懒汉式四种,外加静态内部类和枚举

懒汉式和饿汉式是常规的、比较容易理解的写法,所以有类似通用的逻辑:

  • 私有化构造器,因为要保证该类实例只有一个,所以我们为该类提供一个私有化的构造方法,这样一来,在本类之外将无法new该类对象,所以可以保证该类对象只能在本类中进行实例化。
  • 提供一个唯一实例的变量,在本类外部已经无法new本类对象了,所以我们在本类里面new一个本类实例,并存储在一个成员变量里。注意:还得对这个成员变量进行一些限制,不能被外部修改。
  • 对外暴露一个公开的获取唯一实例的方法,因为在外部已经不能访问到存储唯一实例的成员变量了,所以我们需要提供一个对外的公开方法,用于获取这个唯一实例。

逻辑大体如此,只是一些细节处理不同,所以细分为了六种。另外静态内部类和枚举是利用语言特性来实现的,具体再说

1. 饿汉式

顾名知义,饿汉式,用“饿”来修饰,表明了这个唯一实例初始化的时机。

1.1 静态变量

静态变量,意思就是直接在定义变量的时候,就将这个唯一实例初始化出来。

代码
public class SingletonType01
{// 构造器私有化private SingletonType01(){}//本类内部创建对象实例,这里的final好像不是必须的,因为private表示只能在本类访问,其他类是无法访问的。所更无法修改,反射??private final static SingletonType01 instance = new SingletonType01();// 提供一个公共的构造方法,返回对象实例public static SingletonType01 getInstance(){ return instance; }
}
优缺点分析
  • 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同
    步问题。
  • 缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
  • 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类 装载,这时候初始化instance就没有达到lazy loading的效果
  • 结论:这种单例模式可用,可能造成内存浪费
自主思考
  • 在对这个单例的变量进行限制的时候,是否一定要用final来修饰呢?
    一开始是使用final修饰的,好像没什么问题,但是后面我想到了,这个变量已经是private修饰的了,已经不能被外部访问了,那么更不可能修改了,还用private干嘛呢?后面我想到了反射,反射可以无视封装,所以如果不使用final,应该可以用反射来修改。所以安全起见,最好用。

1.2静态代码块

静态代码块与静态变量类似,只是初始化不是在定义变量的是时候,而是在静态代码块里。

代码
public class SingletonType02
{// 构造器私有化private SingletonType02(){}//本类内部创建对象实例private final static SingletonType02 instance;//公国静态代码块进行初始化赋值static {instance = new SingletonType02();}// 提供一个公共的构造方法,返回对象实例public static SingletonType02 getInstance(){ return instance; }
}
自主思考

和1.1静态变量一样,唯一一点区别是:1.1方式初始化时间比现在这种方式更早一点。
因为类加载的顺序为:父类的(静态变量、静态初始化块)=> 子类的(静态变量、静态初始化块)=> 父类的(变量、初始化块、构造器)=> 子类的(变量、初始化块、构造器)。所以静态变量(常量)的执行顺序是先于静态代码块的。

2. 懒汉式

饿汉式是第一时间就将实例初始化出来了,无论这个实例最终是否被用到。与之相对应,懒汉式是用的时候才进行初始化,可以避免浪费。

2.1 线程不安全

顾名思义,在此种实现方式下,只能在单线程环境下使用,多线程则无法保证实例唯一。

代码
    // 构造器私有化private SingletonType03(){}//在本类中声明变量private static SingletonType03 instance;// 提供一个公共的构造方法,判断并赋值public static SingletonType03 getInstance(){ // 如果为null,就将这个new一个实例并且赋值if (instance == null){ instance = new SingletonType03(); }return instance;}
}
优缺点分析
  • 起到了懒加载的效果,但是只能在单线程下使用。
  • 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
  • 结论:在实际开发中,不要使用这种方式

2.2 同步方法

为了在多线程环境下也能正常使用,所以用同步代码块来进行同步

代码
public class SingletonType04
{// 构造器私有化private SingletonType04(){}//在本类中声明变量private static SingletonType04 instance;// 提供一个公共的构造方法,判断并赋值public static synchronized SingletonType04 getInstance(){if (instance == null){ instance = new SingletonType04(); }return instance;}
}
优缺点分析
  • 在方式2.1的基础上,解决了线程安全问问题。但是效率太低,因为同步了整个方法。
  • 结论:在实际开发中,不推荐使用这种方式

2.3 同步代码块

因为方式2.2同步了整个代码块,效率较低,所以想只同步方法中的部分代码,但是实际上做不动,这个一个错误的实现,不能在多线程下使用!!!但是可以基于这种方式再次改进,所以将这种方式也说一下

代码
public class SingletonType05
{// 构造器私有化private SingletonType05() { }//在本类中声明变量private static SingletonType05 instance;// 提供一个公共的构造方法,用同步机制判断并赋值public static synchronized SingletonType05 getInstance(){if (instance == null){synchronized (SingletonType05.class){ instance = new SingletonType05(); }}return instance;}
}
分析
  • 其实想法是好的,因为同步整个方法效率太低了,如果只用同步一部分的话,效率就可以提上来了
  • 但实际上没有做到线程安全,因为还是有可能多个线程同时进到if里面,然后多次创建对象
  • 但是可以基于这种方式再次改进

2.4 双重检查

对方式2.3的再次改进,可以实现初衷:只同步部分代码,并且可以保证在多线程环境下的安全。

代码
public class SingletonType06
{// 构造器私有化private SingletonType06() { }//在本类中声明变量private static SingletonType06 instance;// 提供一个公共的构造方法,判断并赋值public static synchronized SingletonType06 getInstance(){if (instance == null){synchronized (SingletonType06.class){ if (instance ==null){ instance = new SingletonType06(); }}}return instance;}
}
分析
  • Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两
    次if (singleton == null)检查,这样就可以保证线程安全了。
  • 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),
    直接return实例化对象,也避免的反复进行方法同步.
  • 线程安全;延迟加载;效率较高
  • 结论:在实际开发中,推荐使用这种单例设计模式

3. 其他方式

这些方式是基于语言特性实现的,不过最终都能满足需求。

3.1 静态内部类

使用java中的内部类实现,个人感觉和静态常量类似

代码
// 使用静态内部类完成
public class SingletonType07
{// 构造器私有化private SingletonType07() { }//写一个静态内部类,该类中有一个静态属性SINGLETON_TYPE_07private static class SingletonType07Instance{private static final SingletonType07 SINGLETON_TYPE_07 = new SingletonType07();}// 提供一个公开的方法,返回实例public static synchronized SingletonType07 getInstance(){return SingletonType07Instance.SINGLETON_TYPE_07;}
}
优缺点分析
  • 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  • 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化 时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  • 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  • 结论:推荐使用.

3.2 枚举

使用java的枚举特性实现的单例模式,个人感觉不是很习惯

代码
public enum SingletonType08
{//属性INSTANCE;//定义的方法public void hello(){System.out.println("枚举实现单例模式");}
}
测试
public class Test01
{public static void main(String[] args){SingletonType08 instance1 = SingletonType08.INSTANCE;SingletonType08 instance2 = SingletonType08.INSTANCE;System.out.println(instance1==instance2);instance1.hello();}
}
优缺点分析
  • 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
  • 这种方式是Effective Java作者Josh Bloch 提倡的方式
  • 结论:推荐使用

三 单例在源码中的使用

单例模式在java源码或者框架中有使用,例如

  • Servlet是单例的
  • Spring中的bean默认是单例的

具体的源码就自己去找咯

四 结束语

以上内容的原始为自于韩顺平老师的《图解java设计模式》,我加了一些自己的理解,然后写了这篇博客,如果有什么不对或者不懂的地方,欢迎留言讨论交流。

java设计模式之——单例模式(八种实现)相关推荐

  1. java设计模式之单例模式(七种方法)

    单例模式:个人认为这个是最简单的一种设计模式,而且也是在我们开发中最常用的一个设计模式. 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个 ...

  2. Java设计模式之单例模式(七种写法)

    Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton {private static Singleton ...

  3. JAVA设计模式总结之23种设计模式(重点!!!)

    JAVA设计模式总结之23种设计模式: 一.什么是设计模式 设计模式遵循的原则有6个: 二.设计模式的三个分类 三.各分类中模式的关键点 四.概说23种设计模式 1.单例模式(Singleton) 2 ...

  4. Java设计模式之单例模式的学习

    本篇是本人的第二篇博客 旨在记录本人对于Java设计模式之单例模式的学习和理解,也希望本篇可以对一些正在学习的小伙伴起到一些帮助 单例模式(singleton)的特点: 1.单例模式有且仅有一个实例: ...

  5. Java内存模型 - 同步八种操作

    Java 内存模型 - 同步操作与规则 Java内存模型 - 同步八种操作 锁定(lock): 作用于主内存中的变量,将他标记为一个线程独享变量. 通常意义上的上锁,就是一个线程正在使用时,其他线程必 ...

  6. Java 设计模式(3)单例模式

    前言 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自 ...

  7. 【每天一个java设计模式(十八)】 - 观察者模式

    观察者模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.这种模式有时又称作发布-订阅模式.模型-视图模式,它是对象行为型模式. 观察者 ...

  8. JAVA设计模式总结之23种设计模式

    一.什么是设计模式                                                                                           ...

  9. Java设计模式之单例模式(Singleton Pattern)

    **单例模式:用来创造独一无二的,只能有一个实例的对象设计模式.单例模式确保一个类只有一个实例,并提供一个全局访问点.**相比于全局变量(对对象的静态引用),单例模式可以延迟实例化,而且全局变量不能保 ...

最新文章

  1. 卡顿严重_魔兽怀旧服:安其拉开门二测结果出炉,暴雪依然无法解决卡顿问题...
  2. 链表问题19——合并两个有序的单链表
  3. 不敢相信?System.currentTimeMillis()存在性能问题
  4. 信息系统项目管理师复习第1小时
  5. opencv中图像的基本结构 类型的定义
  6. 武汉大学计算机学院毕业合影,武大校长对毕业合影有求必应
  7. Java中通过NetworkInterface获取主机地址和物理地址等
  8. Django学习笔记之——Forms
  9. 在Virtualbox下为Ubuntu16.04开机自动挂载共享目录的最佳方法
  10. chrome 发送请求出现:Provisional headers are shown 提示
  11. 新硬盘挂载-fdisk+mount案例实操
  12. 勤哲excel服务器点击修改,用勤哲Excel服务器实现工作任务管理系统
  13. java工作流flowable
  14. 微信注册验证成功之后不跳转_微信小号怎么申请(绑定了微信的手机号怎么注册新的微信)...
  15. decimals数据格式化
  16. slf4j日志门面担当
  17. App中英文切换简单好用
  18. 文件或目录损坏且无法读取的解决办法
  19. 链栈(Linked Stack)
  20. Tool for developer

热门文章

  1. go语言实现的一个基于go-zero框架的微服务网盘系统butane-netdisk
  2. 某度网盘不限速下载,这里有个新方法~
  3. js贝塞尔曲线cubic-bezier 模拟实现附UnitBezier.h
  4. 火炬开发区理工学校计算机等级考,我校召开2018下半年全国计算机等级考试考务工作会...
  5. VS2022/VS2019安装WinForm打包程序,Microsoft Visual Studio Installer Projects 2022下载(网盘下载),Installer Project
  6. 做一个程序媛是一种什么体验?
  7. 如何使用Arduino开发板读取KY-037声音检测传感器
  8. ajax技术优点和缺点,介绍Ajax技术
  9. 2万字系统总结,实现Linux命令自由
  10. __dict__属性详解