【设计模式】Java设计模式 - 单例模式

不断学习才是王道

继续踏上学习之路,学之分享笔记

总有一天我也能像各位大佬一样

分享学习心得,欢迎指正,大家一起学习成长!

原创作品,更多关注我CSDN: 一个有梦有戏的人

准备将博客园、CSDN一起记录分享自己的学习心得!!!

简介

单例模式,是java设计模式中最简单的设计模式,是属于创建类型模式。单例模式就是只能有一个实例,即一个类有且仅有一个实例,并且自行实例化向整个系统提供。

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。 -- (美)钟冠贤.Objective-C编程之道 iOS设计模式解析.北京市:人民邮电出版社,2011

UML图:

以下使用多线程测试的代码(如以下例子):

class Threads extends Thread {
@Override
public void run() {
StaticVariableStarve instance = StaticVariableStarve.getInstance();
System.out.println("StaticVariableStarve hashCode: " + instance.hashCode());
}
}

在main方法中调用start。

for (int i = 0; i < 5; i++) {
new Threads().start();
}

1、饿汉式

①、饿汉静态变量

饿汉静态变量通过创建静态变量去实例化对象,在通过静态方法返回实例,其中需要实现私有化构造方法,使得外部不能通过new直接实例化对象。

代码如下:

package com.lyd.demo.singleton;

/**
* @Author: lyd
* @Description: 单例模式 - 饿汉静态变量
* @Date: 2022-08-24
*/
public class StaticVariableStarve {
// 私有化构造方法
private StaticVariableStarve() {
}
// 创建静态变量实例化
private final static StaticVariableStarve singleton = new StaticVariableStarve();
// 返回对象
public static StaticVariableStarve getInstance() {
return singleton;
}
}

饿汉静态变量的测试

单例模式创建只能有一个实例,不管是用几个变量,最后他们都是同一个实例,如下测试,通过“==”判断对象实例是否一致,打印其hashcode可以发现是一样的。

// 饿汉静态变量
StaticVariableStarve singleton1 = StaticVariableStarve.getInstance();
StaticVariableStarve singleton2 = StaticVariableStarve.getInstance();
System.out.println("两者是否相同?" + (singleton1 == singleton2));
System.out.println("singleton1的hashcode:" + (singleton1.hashCode()));
System.out.println("singleton2的hashcode:" + (singleton2.hashCode()));

运行结果:



多线程:

这样写在类装载的时候就已经实例化了,避免了线程安全问题。但是,一开始就已经实例化了,没有使用lazy loading,就会导致有时候这个实例不需要使用,但是他仍然实例化了,这样就造成了内存浪费。

②、饿汉静态代码块

静态代码块中实例化对象,通过类主动去实例化对象,当调用到这个类的时候,静态代码块的代码就会运行,从而实例化对象。

代码如下:

package com.lyd.demo.singleton;

/**
* @Author: lyd
* @Description: 饿汉静态代码块
* @Date: 2022-08-24
*/
public class StaticBlockStarve {
// 私有化构造方法
private StaticBlockStarve() {
}
// 构建静态变量
private static StaticBlockStarve singleton;
static {
singleton = new StaticBlockStarve();
}
// 返回对象
public static StaticBlockStarve getInstance() {
return singleton;
}
}

饿汉静态代码块测试

// 饿汉静态代码块
StaticBlockStarve singleton3 = StaticBlockStarve.getInstance();
StaticBlockStarve singleton4 = StaticBlockStarve.getInstance();
System.out.println("两者是否相同?" + (singleton3 == singleton4));
System.out.println("singleton1的hashcode:" + (singleton3.hashCode()));
System.out.println("singleton2的hashcode:" + (singleton4.hashCode()));

运行结果:



多线程:

2、懒汉式

①、线程不安全

通过 Lazy 初始化,但是线程不安全,这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

代码如下:

通过getInstance()获取实例,在方法内进行判断是否为空,不为空直接返回对象实例。

package com.lyd.demo.singleton;

/**
* @Author: lyd
* @Description: 懒汉式-线程不安全
* @Date: 2022-08-24
*/
public class NotSafeThreadLazy {
// 私有变两个
private static NotSafeThreadLazy singleton;
// 私有构造
private NotSafeThreadLazy() {
}
// 通过方法实例化
public static NotSafeThreadLazy getInstance() {
if (singleton == null) {
singleton = new NotSafeThreadLazy();
}
return singleton;
}
}

测试

// 懒汉线程不安全
NotSafeThreadLazy singleton1 = NotSafeThreadLazy.getInstance();
NotSafeThreadLazy singleton2 = NotSafeThreadLazy.getInstance();
System.out.println("两者是否相同?" + (singleton1 == singleton2));
System.out.println("singleton1的hashcode:" + (singleton1.hashCode()));
System.out.println("singleton2的hashcode:" + (singleton2.hashCode()));

运行结果



多线程:可见生成了多个实例

用到了lazy loading,但只能在单线程下使用。但是在多线程中,可能会导致一个线程已经到达了if判空,但是还没有进行实例化,第二个线程就已经进入判空,并且也进入实例化。这样就会破坏了单例模式,两个实例化就不是同一个,因此这种方式是线程不安全,不推荐使用。

②、线程安全

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

代码如下:

通过lazy loading实例化对象,只在第一次运行实例化,在getInstance方法中使用synchronized保证线程安全。

package com.lyd.demo.singleton;

/**
* @Author: lyd
* @Description: 懒汉式-线程安全
* @Date: 2022-08-24
*/
public class SafeThreadLazy {
// 私有变两个
private static SafeThreadLazy singleton;
// 私有构造
private SafeThreadLazy() {
}
// 通过方法实例化
public static synchronized SafeThreadLazy getInstance() {
if (singleton == null) {
singleton = new SafeThreadLazy();
}
return singleton;
}
}

多线程 :

3、双检锁/双重校验锁(DCL,即 double-checked locking)

采用双锁机制,安全且在多线程情况下能保持高性能。

在变量加上关键字volatile,在实例化的时候用synchronized线程锁,可以使线程安全。

volatile:是Java虚拟机提供的轻量级的同步机制,当某线程更新变量后,其他线程也能感知到。

package com.lyd.demo.singleton;

/**
* @Author: lyd
* @Description: 双检锁/双重校验锁
* @Date: 2022-08-24
*/
public class DoubleCheckedLocking {
// 私有变两个
private static volatile DoubleCheckedLocking singleton;
// 私有构造
private DoubleCheckedLocking() {
}
// 通过方法实例化
public static DoubleCheckedLocking getInstance() {
if (singleton == null) { // [1]
synchronized (DoubleCheckedLocking.class) { // [2]
if (singleton == null) {
singleton = new DoubleCheckedLocking();
}
}
}
return singleton;
}
}

在getInstance()方法中,哪怕是多线程,都能保证线程安全以及单例模式。假如有多个线程,一个线程先到达了[1]判空成功后进入实例化,此时synchronized线程锁就会上锁,在还没实例化后,然而其他线程也过来了,他们也通过了[1]判空,但是在[2]处会被拦住,直到前一个实例化结束后才解锁,等到解锁后,也就已经实例化完成,singleton就已经不再是null。

多线程:

4、静态内部类

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

代码如下:

通过SingletonHolder这个静态内部类实例化,采用类装载的方式进行实例化,但是一开始是不会实例化的,只有显式调用了getInstance(),使得SingletonHolder类被主动调用,从而实例化对象。

package com.lyd.demo.singleton;

/**
* @Author: lyd
* @Description: 静态内部类
* @Date: 2022-08-24
*/
public class StaticInnerClass {
private StaticInnerClass() {}
// 静态内部类
public static class SingletonHolder {
private static final StaticInnerClass SINGLETON = new StaticInnerClass();
}
// 调用方法
public static StaticInnerClass getInstance() {
return SingletonHolder.SINGLETON;
}
}

采用了类装载的方式使实例化只有一个线程。在一开始StaticInnerClass被装载的时候,对象不一定被实例化。需要调用了getInstace()的方法,才会使得SingletonHolder被主动使用,装载SingletonHolder类,也就接着进行实例化对象。

多线程:

5、枚举

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

代码如下:

package com.lyd.demo.singleton;

/**
* @Author: lyd
* @Description:
* @Date: 2022-08-24
*/
public enum Enumeration {
INSTANCE;
public void instanceMethod() {
System.out.println("实例的方法");
}
}

测试

// 枚举
Enumeration instance = Enumeration.INSTANCE;
Enumeration instance2 = Enumeration.INSTANCE;
System.out.println("两者是否相同?" + (instance == instance2));
System.out.println("instance的hashcode:" + (instance.hashCode()));
System.out.println("instance2的hashcode:" + (instance2.hashCode()));
instance.instanceMethod();

结果



多线程测试:



可发现多线程下枚举也是能够实现单例模式。

创作不易,如有错误请指正,感谢观看!记得一键三连哦!

德德小建议:

理解设计模式不是一件简单的事情,需要不断的学习和动手去练习,才能理解。只有掌握好设计模式,才能够真正的理解SpringAOP和Mybatis的底层原理。各位读者可以和我一样,动手敲一敲代码,甚至用不同的例子来做,通过debug一步一步调试,还有就是多看看别人的例子。能够有助于理解!谢谢各位观看指点!️ ️ ️

【设计模式】Java设计模式 - 单例模式的更多相关文章

  1. Unity设计模式+Java设计模式,讲解+案例+PPT,一次性学会设计模式,拥抱高薪!

    一个程序员对设计模式的理解:“不懂”为什么要把很简单的东西搞得那么复杂.后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精髓所在,我所理解的“简单”就是一把钥匙开一把锁的模式, ...

  2. 设计模式 - Java中单例模式的6种写法及优缺点对比

    目录 1 为什么要用单例模式 1.1 什么是单例模式 1.2 单例模式的思路和优势 2 写法① - 饥饿模式 2.1 代码示例 2.2 优缺点比较 3 写法② - 懒惰模式 3.1 代码示例 3.2 ...

  3. 简单工厂设计模式--Java设计模式(一)

    一 概念: 简单工厂模式就是通过一个工厂类根据参数动态创建对应的类. 二 案例 我们以汽车作为例子,在现实生活中汽车只是一个抽象的产品,其中有很多类型的汽车才是具体产品,如奔驰.宝马.保时捷等等(当然 ...

  4. Java设计模式学习资源汇总

    本文记录了Java设计模式学习书籍.教程资源.此分享会持续更新: 1. 设计模式书籍 在豆瓣上搜索了一把,发现设计模式贯穿了人类生活的方方面面.还是回到Java与程序设计来吧. 打算先归类,再浏览,从 ...

  5. Java设计模式知识整理

    1.Java设计模式     Java设计模式分为三种类型,分别是:      ①.创建型设计模式:是对对象创建过程的各种问题和解决方案的总结           包括:静态工厂模式.抽象工厂模式.单 ...

  6. Java设计模式学习笔记(一) 设计模式概述

    前言 大约在一年前学习过一段时间的设计模式,但是当时自己的学习方式比较低效,也没有深刻的去理解.运用所学的知识. 所以现在准备系统的再重新学习一遍,写一个关于设计模式的系列博客. 废话不多说,正文开始 ...

  7. 折腾Java设计模式之中介者模式

    博文原址:折腾Java设计模式之中介者模式 中介者模式 中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性.这种模式提供了一个中介类,该类通常处理不同类之间的通信,并 ...

  8. java设计模式——建造者模式

    一. 定义与类型 定义:将一个复杂对象的构建与它的表示分离,使用同样的构建过程可以创建不同的表示 用户只需制定需要建造的类型就可以得到它们,建造过程以及细节不需要知道 类型:创建型 建造者模式与工厂模 ...

  9. java设计模式之单例模式(几种写法及比较)

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

  10. Java 设计模式 —— 单例模式

    1. 概念: 单例模式是一种常用的软件设计模式.核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果 ...

随机推荐

  1. 【WPF】CAD工程图纸转WPF可直接使用的xaml代码技巧

    前言:随着工业化的进一步发展,制造业.工业自动化等多领域,都可能用到上位监控系统.而WPF在上位监控系统方面,应该算是当下最流行的前端框架之一了.而随着监控体系的不断完善与更新迭代,监控画面会变得越来 ...

  2. sql-DCL用户及权限管理及其他常用命令-oracle

    DCL 用户管理 创建用户 create user 用户名 identified by 密码; 在oracle中要创建一个新的用户使用create user语句,一般是具有dba(数据库管理员)的权限 ...

  3. VScode运行总是显示running状态

    一.每次点击运行都显示code is already running,而且键盘也没有办法输入 二.解决办法 注意:记得重新启动VScode

  4. 密码学系列之:在线证书状态协议OCSP详解

    目录 简介 PKI中的CRL CRL的缺点 CRL的状态 OCSP的工作流程 OCSP的优点 OCSP协议的细节 OCSP请求 OCSP响应 OCSP stapling 总结 简介 我们在进行网页访问 ...

  5. 动画 ---Animejs 简单使用与源码解析

    Anime是什么 Anime有什么用 Anime是作何做的 requireAnimationFrame() engine(){ // 处理让多个帧运动起来 ​ play() ​ step()} ani ...

  6. LMC7660即-5V产生电路

    LMC7660为小功率极性反转电源转换器,通过LMC7660电路产生-5V电压,其芯片管脚定义如下表所示. LMC7660负电压产生电路如下图所示. 其中6脚当供电电压大于等于5V时该脚必须悬空,当供 ...

  7. 攻防世界MISC—进阶区11-20

    11.János-the-Ripper 得到未知类型的文件,010 Editor打开后看到pk,得知是真加密的zip文件. 密码在文件中没有提示,根据题目名字,János-the-Ripper Ján ...

  8. Centos7借助docker部署mysql,提供远程链接服务

    Centos7 借助docker部署mysql,并提供远程连接服务 安装docker 运行docker 注意安装docker和运行docker的步骤很简单,可以参考我学习docker的笔记 docke ...

  9. JavaWeb--基本概念、Web服务器与Tomcat

    前言 Java Web 其实就是一个技术的总和,把Web看成一个容器而已主要使用JavaEE技术来实现.在加上各种中间件. 整个javaWeb阶段的内容通过实际的案例贯穿学习, 所涉及到的技术知识点会 ...

  10. iOS自动化打包 Jenkins+Gitlab+Fastlane+蒲公英+钉钉

    前言 这两天花时间整理一下自动化打包的整套流程,现在iOS端的整套流程是没有问题了,这个过程中踩得坑也的确是特别多,所以这周末把整个流程整理一下,总结出来这篇文章,希望能对有需要的小伙伴有点点帮助. ...