一、单例模式

文章目录

  • [1 基本介绍](#1 基本介绍)
  • [2 实现方式](#2 实现方式)
    • [2.1 饿汉式](#2.1 饿汉式)
      • [2.1.1 代码](#2.1.1 代码)
      • [2.1.2 特性](#2.1.2 特性)
    • [2.2 懒汉式 ( 线程不安全 )](#2.2 懒汉式 ( 线程不安全 ))
      • [2.2.1 代码](#2.2.1 代码)
      • [2.2.2 特性](#2.2.2 特性)
    • [2.3 懒汉式 ( 线程安全 )](#2.3 懒汉式 ( 线程安全 ))
      • [2.3.1 代码](#2.3.1 代码)
      • [2.3.2 特性](#2.3.2 特性)
    • [2.4 双重检查](#2.4 双重检查)
      • [2.4.1 代码](#2.4.1 代码)
      • [2.4.2 特性](#2.4.2 特性)
    • [2.5 静态内部类](#2.5 静态内部类)
      • [2.5.1 代码](#2.5.1 代码)
      • [2.5.2 特性](#2.5.2 特性)
    • [2.6 枚举](#2.6 枚举)
      • [2.6.1 代码](#2.6.1 代码)
      • [2.6.2 特性](#2.6.2 特性)
  • [3 实现的要点](#3 实现的要点)
  • [4 线程不安全的单例模式](#4 线程不安全的单例模式)
    • [4.1 代码](#4.1 代码)
    • [4.2 评价](#4.2 评价)
  • [5 JDK中的单例模式](#5 JDK中的单例模式)
  • [6 单例模式的类图及角色](#6 单例模式的类图及角色)
    • [6.1 类图](#6.1 类图)
    • [6.2 角色](#6.2 角色)
  • [7 推荐的单例模式的实现](#7 推荐的单例模式的实现)
  • [8 单例模式的使用场景](#8 单例模式的使用场景)

1 基本介绍

单例模式 (Singleton Pattern)是一种常用的软件设计模式,其目的是 确保一个类仅有一个实例 ,并提供一个 静态方法 来获取该实例。

2 实现方式

单例模式围绕着两个特性展开:

  • 延迟加载:在需要这个单例时才创建单例,避免浪费内存。
  • 线程安全:在多线程环境下,保证多线程使用的单例是同一个单例。

共有以下六种实现方式:

2.1 饿汉式

2.1.1 代码

饿汉式的实现在 Java 中有两种实现,常用的是第一种。

方式一:显式初始化

java 复制代码
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        return INSTANCE;
    }
}

方式二:静态代码块初始化

java 复制代码
public class Singleton {
    private static final Singleton INSTANCE;
    static {
        INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        return INSTANCE;
    }
}

2.1.2 特性

  • 不延迟加载 :在 类加载 时就创建单例。
  • 线程安全类加载由 JVM 保证线程安全,所以此时创建的单例也是线程安全的。

2.2 懒汉式 ( 线程不安全 )

2.2.1 代码

java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

2.2.2 特性

  • 延迟加载 :只有在 调用获取单例的方法 getInstance() 时才创建单例。
  • 线程不安全 :如果有多个线程同时通过了 singleton == null 这个条件,则它们会创建多个单例。

2.3 懒汉式 ( 线程安全 )

2.3.1 代码

注意 getInstance() 方法前的 synchronized 修饰符。

java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

2.3.2 特性

  • 延迟加载 :只有在 调用获取单例的方法 getInstance() 时才创建单例。
  • 线程安全 :在多个线程同时获取单例时,使用 synchronized 互斥锁,来保证只有一个线程能够生成单例,而其他线程等待这个线程创建的单例,保证了单例的线程安全。
  • 成本太大 :即使已经有单例了,每次调用 getInstance() 方法还得经过 加锁释放锁 的流程(因为使用了 synchronized 互斥锁),降低了并发性能

2.4 双重检查

2.4.1 代码

注意单例前的 volatile 修饰符,它有两个作用:保证变量对所有线程可见防止指令重排 。在此处起 防止指令重排 的作用:防止 JIT 即时编译器对 instance = new Singleton(); 这行代码进行重排序

如果进行重排序,则可能先给 instance 分配内存 (此时 instance != null),然后才调用构造器为 instance 的属性赋值。在这两步操作之间,要是有线程调用 getInstance() 方法,它将无法通过外层的 instance == null 条件,会返回一个不完整(赋值不完全)的对象。

java 复制代码
public class Singleton {
    private static volatile Singleton instance; // 注意 volatile 在这里起 防止指令重排 的作用

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

2.4.2 特性

  • 延迟加载 :只有在 调用获取单例的方法 getInstance() 时才创建单例。
  • 线程安全 :在多个线程同时获取单例 且 单例未创建时,如果都通过了外层的 instance == null 条件,则在内层使用 synchronized 互斥锁,来保证只有一个线程创建单例,而其他线程等待这个线程创建的单例,保证了单例的线程安全。
  • 成本小 :这种实现方式只有最初创建单例时会加互斥锁,之后就不需要创建单例了,直接返回即可,无需加锁和释放锁,提高了并发性能

2.5 静态内部类

2.5.1 代码

java 复制代码
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

2.5.2 特性

  • 延迟加载 :只有在 调用获取单例的方法 getInstance() 时,才会 触发静态内部类的加载,从而创建单例。
  • 线程安全 :静态内部类也是类,类加载由 JVM 保证其线程安全,所以本单例是线程安全的。

2.6 枚举

2.6.1 代码

java 复制代码
public enum Singleton {
    INSTANCE; // 直接使用 Singleton.INSTANCE 就可以获取到单例

	// 可以随意写方法和属性,就像在类中一样
}

2.6.2 特性

  • 不延迟加载 :在 类加载 时就创建单例。
  • 线程安全类加载由 JVM 保证线程安全,所以此时创建的单例也是线程安全的。
  • 防止 反射 或 反序列化 破坏单例 :其他单例的实现都可以通过 反射 或 反序列化 的方式重新创建新的单例,唯独本实现无法使用这两种方式重新创建新的单例,这是因为 枚举无法通过反射获取对象 ,并且 枚举在序列化和反序列化时不会调用构造器 。所以这种实现是 最推荐的

3 实现的要点

  1. 构造器私有化 。例如 private Singleton() {}
  2. 类中有一个 静态 的单例属性。
  3. 提供一个 静态 方法来获取上述单例。

4 线程不安全的单例模式

4.1 代码

java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

4.2 评价

这样的单例模式是 线程不安全 的,synchronized 互斥锁没有起到应有的作用。只要多个线程都能通过 instance == null 条件,则它们每个线程都会创建一次单例,synchronized 仅仅能保证同一时刻只有一个线程在创建单例罢了。

应该将 判断赋值 都放到 synchronized 互斥锁里,就像单例的 第三种实现------懒汉式 ( 线程安全 ) 一样。

5 JDK中的单例模式

在JDK中,Runtime 类使用了 饿汉式单例,代码如下:

java 复制代码
public class Runtime {
    private static final Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}

    // ...
}

6 单例模式的类图及角色

6.1 类图

其中,singleton 属性和 Singleton() 构造器都是 私有的 ,而 getInstance() 方法是 公开的 。此外,singleton 属性和 getInstance() 方法都是 静态的

6.2 角色

在单例模式中,只有一个角色 Singleton,它负责 实现返回单例的 静态 方法

7 推荐的单例模式的实现

  1. 饿汉式
  2. 双重检查
  3. 静态内部类
  4. 枚举

8 单例模式的使用场景

  • 创建对象耗时过多或耗费资源过多(重量级),但经常用到。
  • 频繁访问 数据库文件 的对象。
  • 工具类对象。
相关推荐
弗拉唐1 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi771 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3432 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀2 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20202 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
shuangrenlong2 小时前
slice介绍slice查看器
java·ubuntu
牧竹子2 小时前
对原jar包解压后修改原class文件后重新打包为jar
java·jar
数据小爬虫@2 小时前
如何利用java爬虫获得淘宝商品评论
java·开发语言·爬虫