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

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

Java设计模式 - 单例模式

😄 不断学习才是王道
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 关注我的CSDN: 一个有梦有戏的人
👊 打算连载Java设计模式,记录自己的学习心得,分享学习经验。

今天的内容有点多,也要努力学完!

java设计模式2.jpg

简介

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

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

UML图:
单例模式.jpg

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

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()));

运行结果:
image.png

多线程:
image.png

这样写在类装载的时候就已经实例化了,避免了线程安全问题。但是,一开始就已经实例化了,没有使用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()));

运行结果:
image.png

多线程:
image.png

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()));

运行结果
image.png

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

用到了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;
    }
}

多线程 :
image.png

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。

多线程:
image.png

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类,也就接着进行实例化对象。

多线程:
image.png

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();

结果
image.png

多线程测试:
image.png

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

👍感叹!今天的内容有点多!创作不易,如有错误请指正,感谢观看!记得一键三连哦!👍

💓德德小建议:

理解设计模式不是一件简单的事情,需要不断的学习和动手去练习,才能理解。只有掌握好设计模式,才能够真正的理解SpringAOP和Mybatis的底层原理。各位读者可以和我一样,动手敲一敲代码,甚至用不同的例子来做,通过debug一步一步调试,还有就是多看看别人的例子。能够有助于理解!谢谢各位观看指点!❤️ ❤️ ❤️
相关文章
|
11天前
|
设计模式 监控 Java
设计模式 - 观察者模式(Observer):Java中的战术与策略
【4月更文挑战第7天】观察者模式是构建可维护、可扩展系统的关键,它在Java中通过`Observable`和`Observer`实现对象间一对多的依赖关系,常用于事件处理、数据绑定和同步。该模式支持事件驱动架构、数据同步和实时系统,但需注意避免循环依赖、控制通知粒度,并关注性能和内存泄漏问题。通过明确角色、使用抽象和管理观察者注册,可最大化其效果。
|
3天前
|
设计模式 算法 Java
小谈设计模式(30)—Java设计模式总结
小谈设计模式(30)—Java设计模式总结
|
4天前
|
设计模式 存储 Java
Java设计模式:解释一下单例模式(Singleton Pattern)。
`Singleton Pattern`是Java中的创建型设计模式,确保类只有一个实例并提供全局访问点。它通过私有化构造函数,用静态方法返回唯一的实例。类内静态变量存储此实例,对外仅通过静态方法访问。
12 1
|
8天前
|
设计模式 算法 Java
23种设计模式,模板方法模式的概念优缺点以及JAVA代码举例
【4月更文挑战第10天】模板方法模式是一种行为设计模式,它定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。
11 0
|
9天前
|
设计模式 Java
23种设计模式,状态模式的概念优缺点以及JAVA代码举例
【4月更文挑战第9天】状态模式是一种行为设计模式,允许一个对象在其内部状态改变时改变它的行为,这个对象看起来似乎修改了它的类。
25 4
|
11天前
|
设计模式 Java
23种设计模式,命令模式的概念优缺点以及JAVA代码举例
【4月更文挑战第7天】命令模式是一种行为设计模式,它将请求或简单操作封装为一个对象。这种模式允许用户通过调用对象来参数化其他对象的方法,并能保存、排队和执行方法调用。
16 1
|
6月前
|
设计模式 安全 Java
JAVA设计模式1:单例模式,确保每个类只能有一个实例
JAVA设计模式1:单例模式,确保每个类只能有一个实例
|
6月前
|
设计模式 存储 安全
【设计模式——学习笔记】23种设计模式——单例模式Singleton(原理讲解+应用场景介绍+案例介绍+Java代码实现)
【设计模式——学习笔记】23种设计模式——单例模式Singleton(原理讲解+应用场景介绍+案例介绍+Java代码实现)
24 0
|
4月前
|
设计模式 Java
Java设计模式【一】:单例模式
Java设计模式【一】:单例模式
26 0
|
1月前
|
设计模式 安全 Java
Java设计模式之单例模式
在软件工程中,单例模式是一种常用的设计模式,其核心目标是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。Java作为一门广泛使用的编程语言,实现单例模式是面试和实际开发中的常见需求。
66 9
Java设计模式之单例模式