学习笔记8-单例模式

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

所有的单例模式都具有

1、私有的构造函数,防止在外部被new出。
2、私有的静态成员变量,类型为本类 。
3、公开的一个静态方法,作为新的构造函数,它往往会创建一个对象并保存在静态成员变量里。被称为全局访问点

这是一个单例模式,特点是变量已经初始化,无论是否使用都存在。称之为饿汉式

public class Hungry {
    private Hungry(){
    }
    private final static Hungry hungry = new Hungry();
    public static Hungry getInstance(){
        return hungry;
    }
}

这是一个单例模式,特点是只有被使用时才会调用getInstance()为变量分配内存空间并初始化。称之为懒汉式

public class LazyMan {
    private static LazyMan lazyMan = null;
    private LazyMan(){   
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

getInstance()

getInstance() 是一个在面向对象编程中常见的方法,尤其是在实现单例设计模式时。单例设计模式的目的是控制类的实例化过程,确保在应用程序运行期间某个类只有一个实例存在,并提供一个全局访问点来获取这个实例。

getInstance() 方法通常具有以下特点:

1、静态方法:因为我们需要在不创建类的实例的情况下调用它,所以 getInstance() 定义为类的静态方法。
2、返回类型:该方法的返回类型是它所属的类类型,即返回的是该类的一个实例。
3、确保唯一性:在方法内部,它会检查是否已经存在该类的一个实例。如果是第一次调用,它会创建一个新的实例并记住这个实例(通常通过类的静态变量)。如果之前已经创建过实例,那么它就直接返回之前创建的那个实例,确保了全局只存在一个实例。

static

当static修饰时,其用途都体现了它让成员成为类级别而非对象级别(使用时不用new而是用类名.的方式)的特性:

1、静态变量

  • 当一个变量需要被所有实例共享,即无论创建多少个对象,该变量都只有一份副本时,应使用 static。例如,一个记录对象创建计数的变量。
  • 静态变量属于类本身,不是某个对象的属性,可以通过类名直接访问,无需创建类的实例

2、静态方法

  • 当一个方法不操作任何实例变量,即它的执行不依赖于类的实例状态时,应使用 static。这类方法通常用于工具方法或者工厂方法。
  • 静态方法同样可以直接通过类名调用,无需实例化对象。
  • 注意,静态方法内不能直接访问非静态成员变量或非静态方法,因为这些成员属于对象,而静态方法在没有具体对象的上下文中执行。

在上述代码中,结合getInstance()和static,不难看出,在单例模式中我们会把new出对象的这一操作彻底的对外封闭,只在类的内部进行,而且在类内部会保持new出的对象在内存中的唯一性。

在多线程中

看下面的代码

public class LazyMan {
    private static LazyMan lazyMan = null;
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"创建LazyMan");
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    //多线程测试
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }

}

结果如下,多个线程都new出了对象,破坏了单例
在这里插入图片描述
针对此问题,使用 synchronized (LazyMan.class)进行加锁,确保线程唯一,称之为双重锁定模式(DCL,Double Check Lock)

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

在这里插入图片描述
但是
因为lazyMan = new LazyMan() 这一操作并不是原子性的,过程如下

1、分配对象内存空间
2、执行构造方法初始化对象
3、把对象指向这个空间

在多线程环境中,为了优化性能,编译器和处理器可能会对指令进行重排序,这在某些情况下可能导致程序的行为不符合预期。
也就是说当线程A执行完lazyMan = new LazyMan() 后,在处理器中,原本123的步骤被优化为了132

1、分配对象内存空间
3、把对象指向这个空间lazyMan!=null
此时线程B来了,因为lazyMan!=null,返回lazyMan,但是还没初始化,类内部的属性可能为null,会出现空指针
2、执行构造方法初始化对象

因此,需要禁止指令重排,即使用 volatile 修饰的变量

    // 添加 volatile 关键字
    private volatile static LazyMan lazyMan=null;
    
    public static LazyMan getInstance(){
        synchronized (LazyMan.class){
            if (lazyMan==null){
                lazyMan = new LazyMan();
            }
        }
        return lazyMan;
    }

volatile

在Java中,volatile 关键字主要用于修饰变量,它确保了多线程环境下的可见性和有序性,但不保证原子性。以下是使用 volatile 的典型场景:
1、可见性:当一个线程修改了某个变量的值,其他线程能够立即看到修改后的最新值,而不是各自缓存的旧值。这对于状态标记(比如线程中断标志、是否停止的标志)非常有用,例如:

private volatile boolean stop = false;

public void requestStop() {
    stop = true;
}

public void workerThread() {
    while (!stop) {
        // 执行任务...
    }
}

在上面的例子中,volatile 确保了当其他线程调用 requestStop() 方法修改 stop 标记后,正在执行循环的线程能够立即感知并退出循环。

2、禁止指令重排序:如上述

静态内部类

可以看到,多了一个新类InnerClass,类型为私有静态。
当外部类被加载时,内部类不需要被立即加载,因此也不会去new(初始化)故不会占用内存。
只有当第一次调用getInstance()时才会使虚拟机加载InnerClass,顺势的new出OuterClass 。这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

public class OuterClass {
    private OuterClass(){}
    private static class InnerClass{
        private static final OuterClass STATIC_INNER = new OuterClass();
    }
    private static OuterClass getInstance(){
        return InnerClass.STATIC_INNER;
    }
}

参考:https://blog.csdn.net/mnb65482/article/details/80458571,其详细解释了此方法如何保程安全

枚举

枚举类型:被 enum 关键字修饰
其特性也能实现单例模式

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值