Java设计模式(二)—单例模式

一、定义

    单例模式(Singleton Pattern)是一个比较简单的模式,其本质是确保某一个类只有一个实例,而且自行实例化并向整个应用提供这个实例,该类只提供一个获取对象实例的静态方法

二、类图

三、应用场景:

1.生成唯一序列号的环境
2.在整个项目中需要一个共享访问点或共享数据,例如网站计数器,使用单例模式保持计数器的值,并确保是线程安全的;
3.创建一个对象需要消耗的资源过多,如要 访问IO和数据库等资源;
4.需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(也可以直接声明为static的方式来实现)

四、九种实现方式

1.饿汉式-静态常量

     1).这种实现方式比较简单,在类加载时就完成了对象的实例化,避免了线程同步的问题
     2).虽然避免了线程同步问题,但没有达到懒加载的效果,如果系统中从始至终都不会使用到该实例时,则会造成内存浪费

public class Singleton{
    //静态常量并初始化私有的Singleton对象
    private static final Singleton INSTANCE = new Singleton() ;

    private Singleton() {

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

2.饿汉式-静态代码块

       该实现方式的原理与【饿汉式-静态常量】的实现差不多,只是将单例对象的实例化放在了静态代码块中,也是在类加载时就完成了对象的实例化,避免了线程同步的问题,但也可能带来内存浪费

public class Singleton{
    private static Singleton INSTANCE ;
    //在静态代码块中创建私有对象
    static {
        INSTANCE = new Singleton();
    }
    private Singleton() {

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

3.懒汉式-线程不安全

      1).该种实现方式与饿汉式的两种实现方式相比,实现了懒加载的效果,但是在多线程的场景下会存在线程安全问题
      2).在多线程环境下,当一个线程进入到if (instance == null)判断时,实例对象还未完全创建完成(java在创建对象的步骤)时,另外一个线程也进入if (instance == null)的判断,那么就会产生多个实例对象

public class Singleton {
    private static Singleton instance;
    private Singleton() {

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

4.懒汉式-线程安全(同步方法)

       该种实现方式与【懒汉式-线程不安全】方式相比,解决了线程安全问题,但是每一个线程进入时都需要进行synchronized锁等待,影响了程序的执行效率

public class Singleton {
    private static Singleton instance;
    private Singleton() {

    }
    //同步方法
    public static synchronized Singleton getInstance() {
        if (instance != null) {
            return instance;
        }
        instance = new Singleton_03();
        return instance;
    }
}

5.懒汉式-线程安全(同步代码块)

        原理与【懒汉式-线程安全(同步方法)】一样

public class Singleton {
    private static Singleton instance;
    private Singleton() {

    }
    public static Singleton getInstance() {
        if (instance != null) {
            return instance;
        }
        //同步代码块
        synchronized (Singleton.class) {
            instance = new Singleton_03();
        }
        return instance;
    }
}

6.静态内部类

     1).静态内部类实现的单例模式是一种既能确保线程安全,又能保证单例对象唯一性,并且延迟了单例实例化的方法。它解决了多线程同步问题,同时还可以保证单例的唯一性,不会因为序列化、反射、对象克隆等方式对单例的破坏
     2).静态内部类实现的单例模式其本质在于java虚拟机中类的加载机制
        ①.类的加载是根据类加载器按需加载的,而静态内部类在没有被使用之前不会被加载,所以此时的静态变量INSTANCE不会被初始化 。
        ②.只有在getInstance()静态方法被调用时,静态变量INSTANCE才会被初始化。这个过程是线程安全的,
        ③.由于类的加载是由java虚拟机在类的初始化阶段进行的,而这个阶段会加锁,所以确保了同一时间只有一个线程可以执行这个类的初始化,所以保证了这个过程是线程安全的
     3).静态内部类实现单例模式的优势和局限性
         ①.优势
               ◆实现了懒加载,只有在实际使用时才会创建实例,不会造成资源浪费
               ◆没有使用同步锁,避免了影响效率的问题。
         ②.局限性
               ◆不能传递参数,因为它的构造方法是私有的,外部无法传递参数进去 

public class Singleton {
    //静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton(){

    }
    public static Singleton getInstance(){
        //由Java虚拟机(JVM)来保证线程安全问题
        return SingletonHolder.INSTANCE;
    }
}

7.双重锁检查

     1).双重锁检查是在多线开发中常用的一种方式,经过两次if (instance == null)检查,可以保证线程安全问题
     2).双重锁检查在早期写法存在指令重排序的问题:
         java在创建对象的步骤为:①分配内存空间 ②。初始化对象  ③.将instance指向分配的内存空间,由于指令重排序的问题,可能导致②③步会被颠倒执行顺序
        所以在执行instance = new Singleton()时,由于该操作不是原子性的,②③步执行顺序可能会被颠倒,在执行第一次if(instance== null)判断是,虽然instance!= null,但对象实际上还未创建完成,从而导致其他线程在访问时,最终获取到的是一个不完整的对象
     3).如何解决重排序的问题
      ①.若在定义实例变量instance时如果没有Volatile关键字,单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例
      ②.在Java5之前,虽然在定义实例变量instance时加了Volatile关键字,双重锁检查也是不安全的,因为volatile关键字的语义不够严格,无法完全禁止指令重排序
      ③.在JDK1.5之后,由于JSR-133增强了volatile的内存语义,便在双重锁检查引入了volatile关键字来禁止指令重排序,保证了DCL的正确性。即在声明instance静态变量时。增加volatile关键字修饰,以确保写操作之前的操作不会被重排序到写操作之后
      4).volatile关键字在Java内存模型中的具体作用
        ①.可见性:保证变量的修改对所有线程立即可见。
        ②.禁止指令重排序:通过插入内存屏障,确保编译器和处理器不会对指令进行重排序。
       在双重锁检查中,主要是利用了volatile的第二个特性,即禁止指令重排序,确保对象的初始化在赋值操作之后完成。
      5).程序示例如下:

public class Singleton {
    //静态变量
    private static volatile Singleton instance;
    //私有的构造方法
    private Singleton() {

    }
    //共有的静态方法,对外提供单例对象
    public static Singleton getInstance() {
        //第一次检查判断:避免直接执行同步代码块
        if (instance == null) {
             synchronized(Singleton.class) {
                //第二次判断:确保获取唯一的实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

8.枚举

     1).枚举单例模式在Java中的工作原理基于Java枚举类型的语言特性。它是一种特殊的类型,其底层提供了一组固有的常量实例,枚举单例模式是实现单例设计模式的一种方法,在需要明确的实例控制、线程安全和防止多种攻击的场景中,简化了单例的实现,减少了出错的可能性。
     2).使用枚举来实现单例模式的优势在于一下几点
         ①.线程安全:枚举类型的实例根据java类加载机制,它在Java虚拟机中是唯一的,具备线程安全性
         ②.防止反射攻击:根据反编译枚举类代码的字节码文件可知,枚举类型的构造函数是私有的,且无法通过反射的方式来创建新的实例。
         ③.可以防止反序列化创建新对象:枚举类型在反序列化时不会创建新的实例,只是返回已有的实例
         ④.线程安全:在枚举的单例模式中,实例的创建是由java虚拟机在加载枚举类时完成的,所以是自然的线程安全的。因此,枚举单例不需要额外的同步机制来防止多线程下的多重实例化问题
         ⑤.性能优势:由于枚举单例不需要复杂的同步或者延迟加载机制,它通常能够在性能上有所优势,特别是在高并发场景下。
    3).枚举单例模式虽然有以上所属的优势,但其也存在一定的局限性,主要包括如下几点:
         ①.不支持继承:无法继承其他类,因为其底层默认已经继承了Enum 类,这在某些特定情况下可能会限制其使用。
         ②.不支持懒加载:枚举单例在类加载时就被实例化,这意味着它们不支持懒加载。若单例初始化包含了重量级资源分配或配置加载,然而在不需要这些资源时提前加载可能会导致性能问题
         ③.反射的限制:在使用一些高级的反射技术时在枚举类型上可能不适用,比如枚举的构造函数是私有的,反射创建枚举实例会抛出异常等
      综上所属,在使用枚举类型来实现单例模式时,需要根据实际的业务场景来决定。尽管使用枚举来实现单例模式是Effective Java的作者Josh Bloch所提倡的方式

public enum Singleton {
    INSTANCE;
    //枚举中定义方法
    public void methodImplement() {
        System.out.println("do something");
    }
    public static void main(String[] args) {
        //枚举的调用方式
        Singleton.INSTANCE.methodImplement();
    }
}

9.CAS-AtomicReference

      1).CAS的基本原理是:当多个线程尝试使用CAS同时去更新同一个变量时,同一时间只能有其中一个线程能更新变量的值,而其它线程都会失败,但失败的线程并不会被挂起,而是被告知其竞争中失败,并可以再次尝试,其体现的是乐观锁
      2).使用CAS的好处在于不需要使用传统的锁机制来保证线程安全
      3).CAS其本质是一种基于忙等待的算法,其依赖底层硬件的实现,相对于锁的实现,CAS没有线程切换和阻塞所带来的的额外消耗,同时可以支持较大的并行度。
      4).但是CAS的一个重要缺点:就在于如果忙等待一直执行不成功,那么程序会一直在死循环中进行等待,那么会对CPU带来较大的执行开销。

public class Singleton {
    private static Singleton instance;

    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();

    private Singleton() {

    }
    public static Singleton getInstance() {
        for(;;){
            Singleton singleton = INSTANCE.get();
            if(singleton != null){
                return singleton;
            }
            INSTANCE.compareAndSet(null, new Singleton());
            return INSTANCE.get();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值