单例模式学习心得

简介

  • 什么是单例
    一个类只有一个实例,并且提供一个全局访问点

  • 为什么需要单例模式

    • 对于一些简单类型对象的创建如:int room = 806 、String school = "edu.zsc.bdic" 这些类型的对象创建和销毁对性能影响不大

    • 有些对象的创建过程是庞大而复杂的,并且这些对象完全是可以复用的,如果频繁的创建和销毁将影响性能,如:

      • 数据库连接对象

      • 配置对象

  • 实现单例模式需要考虑那些点
    1、是否是线程安全的:在多线程并发情况下是否仍能保证获取同一实例
    2、是否支持懒加载:如果在类加载的时候就实例化,万一一直不使用则容易浪费内存,我们希望在调用的时候再实例化,这是更加合理的做法。
    3、是否被反射破环(这种方式属于人为破坏,暂时不考虑)

单例实现方式

单例模式实现方法的通式:1.构造方法私有化 2.提供全局访问点

1.饿汉式

//饿汉式1
public class Singleton1 {
    //将实例声明为静态常量,在类加载的时候就实例化
    private static final Singleton1 instance = new Singleton1();

    // 私有构造函数,防止实例化
    private Singleton1() {
    }
    // 提供全局访问点
    public static Singleton1 getInstance(){
        return instance;
    }

}

是否线程安全:√

是否支持懒加载:×

2.懒汉式

//懒汉式1
public class Singleton1 {
    private static Singleton1 instance;

    // 私有构造函数,防止实例化
    private Singleton1() {
        
    }

    public static getInstance(){
        //此处存在线程安全问题,可能多个线程同时进入if语句块
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}
public class Test {
    public static void main(String[] args) {
        Thread t1 = new MyThread();
        Thread t2 = new MyThread();
        t1.start();
        t2.start();

    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Singleton1.getInstance());
    }
}

//两个线程的输出结果
edu.zsc.bdic.singleton2.Singleton1@4e704f06
edu.zsc.bdic.singleton2.Singleton1@65d2066b

是否线程安全:×

是否支持懒加载:√

3.懒汉式2(校验锁)

//懒汉式2 
public class Singleton2 {
    /*
        volatile 关键字确保 instance 的可见性和有序性
        它防止了指令重排序可能导致的问题,使得一个线程看到一个未完全初始化的对象
     */
    private static volatile Singleton2 instance;

    private Singleton2() {
        // 私有构造函数,防止实例化
    }

    public static synchronized Singleton1 getInstance(){
        //此处存在线程安全问题,可能多个线程同时进入if语句块 需要加锁,但是会影响性能
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

是否线程安全:√

是否支持懒加载:√

线程需要排队,性能一般

4.懒汉式3(双重校验锁)

//懒汉式3 DCL(双重锁)
public class Singleton2 {
    /*
        volatile 关键字确保 instance 的可见性和有序性
        它防止了指令重排序可能导致的问题,使得一个线程看到一个未完全初始化的对象
     */
    private static volatile Singleton2 instance;

    private Singleton2() {
        // 私有构造函数,防止实例化
    }

    public static Singleton2 getInstance(){
        if (instance == null) {  //一重校验:检查是否已经存在实例,避免进入同步代码块
            synchronized (Singleton2.class){
                if (instance == null) {  //二重校验:避免第一个已经创建了实例的线程释放了锁后,其他线程重复创建实例
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }
}
instance = new Singleton2();
最主要是因为这一行代码不具备原子性
它可以被差分为三部分:
  1.分配内存
  2.初始化对象
  3.设置引用指向堆中的对象地址
jvm重排序的时候会打乱它们的顺序,使一个线程看到一个未完全初始化的对象

是否线程安全:√

是否支持懒加载:√

实现比较复杂

5.静态内部类

//静态内部类
public class Singleton1 {

    private Singleton1() {
        // 私有构造函数,防止实例化
    }

    // 静态内部类运行机理:JVM加载类的时候是不会加载静态内部类
    // 只有当内部类的属性/方法被访问的时候才会加载
    private static class SingletonHolder{
        private static final Singleton1 INSTANCE = new Singleton1();
    }

    public static Singleton1 getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

这种写法巧妙的利用了jvm加载类的机制:静态内部类不会随着外部类的加载而自动加载。JVM在加载包含静态内部类的外部类时,并不会立即加载静态内部类。只有当静态内部类被显式引用时,JVM才会加载它。

是否线程安全:√

是否支持懒加载:√

心得

单例模式作为设计模式里面一种简单的模式,它包含了线程安全、内存模型、类加载机制等等一些核心的知识点。

认识到了单例模式的几种方式:饿汉式,懒汉式,双重校验锁,静态内部类,也了解到了它们各自的特点。

虽然单例模式是一种简单的设计模式,但是它考验了程序员的基本功是否扎实,在学习的过程中我发现了自己对这方面的了解确实有所欠缺。

参考

https://mp.weixin.qq.com/s/nKsKOIdV-vt-tAEs5mqlpw
B站:BV1pt4y1X7kt


单例模式学习心得
http://localhost:8090//archives/dan-li-mo-shi-xue-xi-xin-de
作者
LinWJ
发布于
2024年09月21日
许可协议