【设计模式】学习笔记---单例模式

概述

学习设计模式,死记硬背是没用的,还要从实践中理解

日常应用中,设计模式从来都不是单个设计模式独立使用的。在实际应用中,通常多个设计模式混合使用,你中有我,我中有你。下图完整地描述了设计模式之间的混用关系,希望对大家有所帮助。

在这里插入图片描述

单例模式

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

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

注意:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。

单例模式的应用场景

对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。

  1. 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  2. 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  3. 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  4. 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  5. 频繁访问数据库或文件的对象。
  6. 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
  7. 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

懒汉模式

延迟加载, 只有在真正使用的时候,才开始实例化。

1)线程安全问题

2)double check 加锁优化

3)编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化 的实例,可以通过添加volatile 关键字进行修饰, 对于volatile 修饰的字段,可以防止指令重排。

class LazySingleton{ 

    // java5版本之前 关键字volatile 不能解决指令重排的问题
    private volatile static LazySingleton instance; 
    
    // 让构造函数为 private,这样该类就不会被实例化
    private LazySingleton(){ 
        
    } 
    
    // 这种方式的懒加载只适用于单线程情况下
    public static LazySingleton getInstance() { 
        if (instance==null){ 
            instance=new LazySingleton(); 
        } 
        return instance; 
    } 
    
    // 同步,线程安全,但是效率太低
    // 但是单例模式只有在第一次使用的时候才会创建
    // 下面这种写法就不适合了
    public static synchronized LazySingleton getInstance() { 
        if (instance==null){ 
            instance=new LazySingleton(); 
        } 
        return instance; 
    } 
    
    // 双重检验锁
    // 下面代码看是完美,但是JVM编译器存在指令重排的优化(有坑)
    public static LazySingleton getInstance() { 
        if (instance==null){ 
            synchronized (LazySingleton.class){ 
                if (instance==null){ 
                    instance=new LazySingleton(); 
                } 
            } 
        } 
        return instance; 
    } 
}

饿汉模式

类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。

类加载过程

1,加载二进制数据到内存中, 生成对应的Class数据结构,

2,连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析

3,初始化: 给类的静态变量赋初值

只有在真正使用对应的类时,才会触发初始化 如( 当前类是启动类即 main函数所在类,直接进行new 操作,访问静态属性、访问静态方 法,用反射访问类,初始化一个类的子类等.)

class HungrySingleton{ 

    private static HungrySingleton instance=new HungrySingleton(); 
    
    //让构造函数为 private,这样该类就不会被实例化
    private HungrySingleton(){ 
    
    } 
    
    public static HungrySingleton getInstance() { 
        return instance;
    } 
}

静态内部类

1).本质上是利用类的加载机制来保证线程安全

2).只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一 种形式。

class InnerClassSingleton{ 
    private static class InnerClassHolder{
        private static InnerClassSingleton instance= new InnerClassSingleton(); 
        
    } 
    
    private InnerClassSingleton(){
        
    } 
    
    public static InnerClassSingleton getInstance(){ 
        return InnerClassHolder.instance;
    }
}

反射攻击实例

Constructor<InnerClassSingleton> declaredConstructor=InnerClassSingleton.c lass.getDeclaredConstructor(); 
declaredConstructor.setAccessible( true ); 
InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance(); 
InnerClassSingleton instance=InnerClassSingleton.getInstance(); 
System.out.println(innerClassSingleton==instance);

静态内部类防止反射破坏

class InnerClassSingleton { 
    private static class InnerClassHolder{ 
        private static InnerClassSingleton instance= new InnerClassSingleton(); 
    } 
    
    private InnerClassSingleton(){ 
        if (InnerClassHolder.instance!=null){ 
            throw new RuntimeException( " 单例不允许多个实例 " ); 
        } 
    } 
            
    public static InnerClassSingleton getInstance(){ 
        return InnerClassHolder.instance; 
    } 
}

枚举类型

1)天然不支持反射创建对应的实例,且有自己的反序列化机制

2)利用类加载机制保证线程安全

public enum EnumSingleton { 
    INSTANCE; 
    
    public void print(){ 
        System.out.println(this.hashCode()); 
    } 
}

序列化

1)可以利用 指定方法来替换从反序列化流中的数据 如下 1

ANY‐ACCESS‐MODIFIER Object readResolve() throws ObjectStreamException; 

class InnerClassSingleton implements Serializable{ 

    static final long serialVersionUID = 42L; 
    
    private static class InnerClassHolder{ 
    
        private static InnerClassSingleton instance= new InnerClassSingleton(); 
    } 
    
    private InnerClassSingleton(){ 
        if (InnerClassHolder.instance!=null){ 
            throw new RuntimeException( " 单例不允许多个实例 " );
        }
    } 
    
    public static InnerClassSingleton getInstance(){ 
        return InnerClassHolder.instance; 
    } 
    
    Object readResolve() throws ObjectStreamException{ 
        return InnerClassHolder.instance; 
    } 
    
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值