前言

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

饿汉单例

是否多线程安全:是

是否懒加载:否

正如名字含义,饿汉需要直接创建实例。

public class EhSingleton {

    private static EhSingleton ehSingleton = new EhSingleton();

    private EhSingleton() {}

    public static EhSingleton getInstance(){
return ehSingleton;
}
}

缺点: 类加载就初始化,浪费内存

优点: 没有加锁,执行效率高。还是线程安全的实例。

懒汉单例

懒汉单例,在类初始化不会创建实例,只有被调用时才会创建实例。

非线程安全的懒汉单例

是否多线程安全:否

是否懒加载: 是

public class LazySingleton {

    private static LazySingleton ehSingleton;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
if (ehSingleton == null) {
ehSingleton = new LazySingleton();
}
return ehSingleton; } }

实例在调用 getInstance 才会创建实例,这样的优点是不占内存,在单线程模式下,是安全的。但是多线程模式下,多个线程同时执行 if (ehSingleton == null) 结果都为 true,会创建多个实例,所以上面的懒汉单例是一个线程不安全的实例。

加同步锁的懒汉单例

是否多线程安全:是

是否懒加载: 是

为了解决多个线程同时执行 if (ehSingleton == null) 的问题,getInstance 方法添加同步锁,这样就保证了一个线程进入了 getInstance 方法,别的线程就无法进入该方法,只有执行完毕之后,其他线程才能进入该方法,同一时间只有一个线程才能进入该方法。

public class LazySingletonSync {

    private static LazySingletonSync lazySingletonSync;

    private LazySingletonSync() {}

    public static synchronized LazySingletonSync getInstance() {
if (lazySingletonSync == null) {
lazySingletonSync =new LazySingletonSync();
}
return lazySingletonSync;
} }

这样配置虽然保证了线程的安全性,但是效率低,只有在第一次调用初始化之后,才需要同步,初始化之后都不需要进行同步。锁的粒度太大,影响了程序的执行效率。

双重检验懒汉单例

是否多线程安全:是

是否懒加载:是

使用 synchronized 声明的方法,在多个线程访问,比如A线程访问时,其他线程必须等待A线程执行完毕之后才能访问,大大的降低的程序的运行效率。这个时候使用 synchronized 代码块优化执行时间,减少锁的粒度

双重检验首先判断实例是否为空,然后使用 synchronized (LazySingletonDoubleCheck.class) 使用类锁,锁住整个类,执行完代码块的代码之后,新建了实例,其他代码都不走 if (lazySingletonDoubleCheck == null) 里面,只会在最开始的时候效率变慢。而 synchronized 里面还需要判断是因为可能同时有多个线程都执行到 synchronized (LazySingletonDoubleCheck.class) ,如果有一个线程线程新建实例,其他线程就能获取到 lazySingletonDoubleCheck 不为空,就不会再创建实例了。

public class LazySingletonDoubleCheck {

    private static LazySingletonDoubleCheck lazySingletonDoubleCheck;

    private LazySingletonDoubleCheck() {}

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

静态内部类

是否多线程安全:是

是否懒加载:是

外部类加载时,并不会加载内部类,也就不会执行 new SingletonHolder(),这属于懒加载。只有第一次调用 getInstance() 方法时才会加载 SingletonHolder 类。而静态内部类是线程安全的。

静态内部类为什么是线程安全

静态内部类利用了类加载机制的初始化阶段 方法,静态内部类的静态变量赋值操作,实际就是一个 方法,当执行 getInstance() 方法时,虚拟机才会加载 SingletonHolder 静态内部类,

然后在加载静态内部类,该内部类有静态变量,JVM会改内部生成方法,然后在初始化执行方法 —— 即执行静态变量的赋值动作。

虚拟机会保证 方法在多线程环境下使用加锁同步,只会执行一次 方法。

这种方式不仅实现延迟加载,也保障线程安全。

public class StaticClass {

    private StaticClass() {}

    private static class SingletonHolder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
} public static final SingletonHolder getInstance() {
return SingletonHolder.INSTANCE;
}
}

总结

  • 饿汉单例类加载就初始化,在没有加锁的情况下实现了线程安全,执行效率高。但是无论有没有调用实例都会被创建,比较浪费内存。
  • 为了解决内存的浪费,使用了懒汉单例,但是懒汉单例在多线程下会引发线程不安全的问题。
  • 不安全的懒汉单例,使用 synchronized 声明同步方法,获取实例就是安全了。
  • synchronized 声明方法每次线程调用方法,其它线程只能等待,降低了程序的运行效率。
  • 为了减少锁的粒度,使用 synchronized 代码块,因为只有少量的线程获取实例,实例是null,创建实例之后,后续的线程都能获取到线程,也就无需使用锁了。可能多个线程执行到 synchronized ,所以同步代码块还需要再次判断一次。
  • 静态内部类赋值实际是调用 方法,而虚拟机保证 方法使用锁,保证线程安全。

五种方式实现 Java 单例模式的更多相关文章

  1. 五种方式让你在java中读取properties文件内容不再是难题

    一.背景 最近,在项目开发的过程中,遇到需要在properties文件中定义一些自定义的变量,以供java程序动态的读取,修改变量,不再需要修改代码的问题.就借此机会把Spring+SpringMVC ...

  2. Java中创建(实例化)对象的五种方式

    Java中创建(实例化)对象的五种方式1.用new语句创建对象,这是最常见的创建对象的方法. 2.通过工厂方法返回对象,如:String str = String.valueOf(23); 3.运用反 ...

  3. 【开发笔记】- Java读取properties文件的五种方式

    原文地址:https://www.cnblogs.com/hafiz/p/5876243.html 一.背景 最近,在项目开发的过程中,遇到需要在properties文件中定义一些自定义的变量,以供j ...

  4. Java Map集合 遍历 五种方式(包含 Lambda 表达式遍历)

    示例代码如下: package com.miracle.luna.lambda; import java.util.HashMap; import java.util.Iterator; import ...

  5. 【Java基础】Java创建对象的五种方式

    Java中创建(实例化)对象的五种方式 1.用new语句直接创建对象,这是最常见的创建对象的方法. 2.通过工厂方法返回对象,如:String str = String.valueOf(23); 3. ...

  6. Spring事务配置的五种方式(转载)

    Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分. DataSo ...

  7. Spring事务配置的五种方式

    Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分. DataSo ...

  8. Spring事务配置的五种方式 -- 越往后需要Spring版本越高

    第五种 基本零配置  个人感觉第四种也可以 Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式, ...

  9. Spring事务配置的五种方式(转)

    前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学习发觉Spring的事务配置只要把思路理清,还是比较好掌握的. ...

随机推荐

  1. php实验一专属跳转博文

    今天完成了php关于设计个人博客主页的实验一作业. 这是php实验一作业中博客的跳转链接页.  

  2. 图数据库|正反向边的最终一致性——TOSS 介绍

    本文首发于 Nebula Graph Community 公众号 Nebula Graph v2.6 当中比较重要的特性之一便是 TOSS.通过本文,我将带你全方位了解 TOSS 为何物. 从一条 G ...

  3. Linux中,MySQL的常用命令

    我的博客 登录 mysql -u用户名 -p -- 然后在下面输入密码,Linux的密码不会显示出,盲打就可以 mysql -u用户名 -p密码 -- 这种方式将直接登录 开关 开启数据库 servi ...

  4. 变量命名 函数命名 方法 Naming cheatsheet

    Naming things is hard. This sheet attempts to make it easier. Although these suggestions can be appl ...

  5. C++实现矩阵类和向量类

    C++期末作业内容,写完之后觉得过于臃肿,又重新搞了个新的.新的当作业交,旧的拿来给同学参考. [问题描述]请仿照复数类,设计一个矩阵类,设计矩阵类的构成元素 1.编写构造函数完成初始化 2.编写成员 ...

  6. 《转载》python/人工智能/Tensorflow/自然语言处理/计算机视觉/机器学习学习资源分享

    本次分享一部分python/人工智能/Tensorflow/自然语言处理/计算机视觉/机器学习的学习资源,也是一些比较基础的,如果大家有看过网易云课堂的吴恩达的入门课程,在看这些视频还是一个很不错的提 ...

  7. [题解][YZOJ50074] 小 C 的岛屿

    仅仅是对 \(O(n^4)\) 做法的一个记录. 简要题意 有 \(N\) 座岛屿,初始时没有边.每座岛屿都有一个概率值 \(p_i\) 和一个大小为 \(s_i\) 友好列表 \(A_i\) . 小 ...

  8. tuandui last

    组长博客链接### 组长博客 参考邹欣老师的问题模板进行总结思考### 设想和目标(2分)#### 1.我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 解决的问题 ...

  9. ONNXRuntime学习笔记(三)

    接上一篇完成的pytorch模型训练结果,模型结构为ResNet18+fc,参数量约为11M,最终测试集Acc达到94.83%.接下来有分两个部分:导出onnx和使用onnxruntime推理. 一. ...

  10. 基于STM32+华为云IOT设计智能称重系统

    摘要:选择部署多个重量传感器和必要的算法.通过WiFi 通信模块.GPS定位模块,采集车辆称重数据一地理位置信息,并通过网络发送至云平台,设计图形化UI界面展示称重.地图位置等重要信息,实现对称重系统 ...