设计模式----单例模式
创始人
2024-01-21 09:06:39

设计模式之单例模式

文章目录

    • 设计模式之单例模式
      • 一. 简介
        • 1. 什么是单例模式?
        • 2. 单例模式的应用场景?
        • 3. 单例模式的类型?
      • 二. 单例模式的几种写法
        • 1. 饿汉式
        • 2. 懒汉式
        • 3. 懒汉式(线程安全+性能优化)
        • 4. 使用volatile防止指令重排
        • 5. 登记式/静态内部类
        • 6. 枚举
        • 7. 粉碎懒汉式单例与饿汉式单例


一. 简介

1. 什么是单例模式?

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

2. 单例模式的应用场景?

  • 网页中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 要求生产唯一序列号。
  • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等

3. 单例模式的类型?

  • 懒汉式:在真正需要使用对象时才去创建该单例类对象
  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

二. 单例模式的几种写法

1. 饿汉式

饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。

// 饿汉式
class Singleton {//1.私有化构造器函数private Singleton() {}//2.创建本类对象并指向本类引用private final static  Singleton instance = new Singleton();//3.提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {return instance;}
}

优点:

  • 这种写法比较简单,就是在类装载的时候就完成了实例化。避免了线程同步问题。
  • 在类加载的同时已经创建好一个静态对象,调用时反应速度快。
  • 线程安全

缺点:

  • 来类装载的时候就完成了实例化,没有达到Lazy Loading的效果。如果从始至终未使用过这个实例,则会造成实例的浪费。

2. 懒汉式

懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。,否则则先执行实例化操作。


public class Singleton {//1.私有化构造器函数private Singleton(){}//2.先不创建对象private static Singleton instance ;//3.提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {//4.如果instance==null 的时候再去创建,否则直接返回if (instance == null) {instance = new Singleton();}return instance ;}}  

缺点:

  • 线程不安全,多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及 往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

在这里插入图片描述

3. 懒汉式(线程安全+性能优化)

要想懒汉式线程安全,最容易想到的方法就是加锁。


public class Singleton {//1.私有化构造器函数private Singleton() {}//2.先不创建对象private static Singleton instance;//3.提供一个公有的静态方法,返回实例对象public static synchronized Singleton getInstance() {//4.如果instance==null 的时候再去创建,否则直接返回if (instance == null) {instance = new Singleton();}return instance;}
}

这样就规避了两个线程同时创建Singleton对象的风险,但是引来另外一个问题:每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象。

性能优化:


public class Singleton {//1.私有化构造器函数private Singleton() {}//2.先不创建对象private static Singleton instance;//3.提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {//4.线程A和线程B同时看到singleton = null,如果不为null,则直接返回singletonif (instance == null) {//5.线程A或线程B获得该锁进行初始化synchronized (Singleton.class) {if (instance == null) {//6.其中一个线程进入该分支,另外一个线程则不会进入该分支instance = new Singleton();}}}return instance;}
}

上面这段代码已经近似完美了,但是还存在最后一个问题:指令重排

4. 使用volatile防止指令重排

指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能

创建一个对象,在JVM中会经过三步:

  1. 为singleton分配内存空间
  2. 初始化singleton对象
  3. 将singleton指向分配好的内存空间

在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。

在这里插入图片描述

使用volatile关键字可以防止指令重排序,​其原理较为复杂,这篇博客不打算展开,可以这样理解:使用volatile关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换,这样在多线程环境下就不会发生NPE异常了。

volatile还有第二个作用:使用volatile关键字修饰的变量,可以保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。

最终的代码如下所示:

public class Singleton {//1.私有化构造器函数private Singleton() {}//2.使用volatile关键字修饰的变量private static volatile Singleton instance;//3.提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {//4.线程A和线程B同时看到singleton = null,如果不为null,则直接返回singletonif (instance == null) {//5.线程A或线程B获得该锁进行初始化synchronized (Singleton.class) {if (instance == null) {//6.其中一个线程进入该分支,另外一个线程则不会进入该分支instance = new Singleton();}}}return instance;}
}

5. 登记式/静态内部类

这种方式能达到双检锁方式一样的功效,并且线程是安全的,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

public class Singleton {// 1. 私有化构造函数private Singleton (){}// 2. 使用SingletonHolder类装载Singleton类private static class SingletonHolder {private static final Singleton instance = new Singleton();}//3.提供一个公有的静态方法,返回装载Singleton的类public static final Singleton getInstance() {return SingletonHolder.instance;}
}

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

想象一下,如果实例化,instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第3 种方式就显得很合理。

6. 枚举

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。 这种方式是 Effective Java 作者 Josh Bloch
提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

示例如下:

public enum Singleton {  INSTANCE;  public void whateverMethod() {  }  
}

7. 粉碎懒汉式单例与饿汉式单例

无论是完美的懒汉式还是饿汉式,终究敌不过反射和序列化,它们俩都可以把单例对象破坏掉(产生多个对象)。

  1. 利用反射破坏单例模式
public static void main(String[] args) {// 获取类的显式构造器Constructor construct = Singleton.class.getDeclaredConstructor();// 可访问私有构造器construct.setAccessible(true); // 利用反射构造新对象Singleton instance1= construct.newInstance(); // 通过正常方式获取单例对象Singleton instance2= Singleton.getInstance(); System.out.println(instance1== instance2); // false
}

利用反射,强制访问类的私有构造器,去创建另一个对象

  1. 利用序列化与反序列化破坏单例模式

public static void main(String[] args) {// 创建输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));// 将单例对象写到文件中oos.writeObject(Singleton.getInstance());// 从文件中读取单例对象File file = new File("Singleton.file");ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));Singleton newInstance = (Singleton) ois.readObject();// 判断是否是同一个对象System.out.println(newInstance == Singleton.getInstance()); // false
}

两个对象地址不相等的原因是:readObject() 方法读入对象时,它必定会返回一个新的对象实例,必然指向新的内存地址

相关内容

热门资讯

长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
不用扬鞭自奋蹄 乘势而上千帆竞... 思明区正全力向“两个进位”目标发起冲刺,努力为建设高素质高颜值现代化国际化的厦门作出“思明贡献”。(...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
好听的民谣歌曲 2021最火的... #我的年度歌单#在2021年里听过很多民谣,但是最能触及心中温柔的还是下面的几首。不听民谣的理由有很...