单例模式

  • 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
  • 单例模式是创建型模式

饿汉单例模式

  • 饿汉单例模式在类的加载时候就立即初始化,并且创建对象。
  • 它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题

优点:没有加任何锁、执行效率比较高,用户体验比懒汉单例模式更好
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能“占着茅坑不拉屎”

package org.example.spring.designpattern.singleton;

/**
 * TODO 饿汉单例模式
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/9 08:16
 */
public class HungrySingleton {
    //先静态、后动态
    //先属性、后方法
    //先上后下
    /**
     * 全局访问点,线程先没出现以前就实例化,不管用不用都占用空间
     */
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {
    }

    // 提供一个全局访问点
    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

还有另一种写法,利用静态代码块的机制:


package org.example.spring.designpattern.singleton;

/**
 * TODO 饿汉单例模式——静态代码块
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/9 08:19
 */
public class HungryStaticSingleton {
    /**
     * 提供一个全局访问点
     */
    private static final HungryStaticSingleton INSTANCE ;
    
    static {
        INSTANCE = new HungryStaticSingleton();
    }

    private HungryStaticSingleton() {
    }
    
    public static HungryStaticSingleton getInstance() {
        return INSTANCE;
    }
}

饿汉单例模式适合用于单例对象比较少的情况

懒汉单例模式

懒汉单例模式,顾名思义它......比较懒,所以它的特点是:被外部类调用的时候内部类才会加载。

package org.example.spring.designpattern.singleton;

/**
 * TODO 懒汉单例模式
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/9 08:23
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton() {
    }

    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;

    /**
     * 静态方法
     * 实例不存在的时候new一个
     * @return
     */
    public static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }


    /**
     * 创建一个线程
     */
    static class testSingletonThread implements Runnable{
        @Override
        public void run() {
            LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + singleton);
        }
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(new testSingletonThread());
        Thread t2 = new Thread(new testSingletonThread());
        t1.start();
        t2.start();
        System.out.println("End");
    }
}

运行结果如下:
Spring设计模式——单例模式-小白菜博客
以上执行结果有概率出现两种不同结果,存在很大的线程安全隐患;这里教大家一种新技能,用线程模式调试,手动控制线程的执行顺序来跟踪内存的变化。

线程模式调试

先给testSingletonThread打上断点,如下图所示:
Spring设计模式——单例模式-小白菜博客
使用鼠标右键单击断点,切换为Thread模式,如下图所示:
Spring设计模式——单例模式-小白菜博客
然后给LazySimpleton类打上断点,同样标记为Thread模式,如下图所示:
Spring设计模式——单例模式-小白菜博客
转到客户端测试代码,同样也打上断点,同时改为Thread模式,如下图所示:
Spring设计模式——单例模式-小白菜博客
开始Debug之后,会看到Debug控制台可以自由切换Thread的运行状态:
Spring设计模式——单例模式-小白菜博客
通过不断切换线程,并观测其内存状态,我们发现在线程环境下LazySimpleSingleton被实例化了两次。有时候我们得到的运行结果可能是相同的两个对象,实际上是被后面执行的线程覆盖了,我们看到了一个假象,线程安全隐患依旧存在。

那么,我们如何来优化代码,使得懒汉单例模式在线程环境下安全呢?看下面的代码,给getInstance()加上synchronized关键字,使这个方法变成线程同步方法:
Spring设计模式——单例模式-小白菜博客
双重检查锁懒汉单例模式:

package org.example.spring.designpattern.singleton;

/**
 * TODO 双重检查锁懒汉单例模式
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/9 08:48
 */
public class LazyDoubleCheckSingleton {
    //静态块,公共内存区域
    private static LazyDoubleCheckSingleton lazy = null;
    private LazyDoubleCheckSingleton() {
        
    }
    public static LazyDoubleCheckSingleton getInstance() {
        if (lazy == null) {
            synchronized (LazyDoubleCheckSingleton.class){
                if (lazy == null) {
                    lazy = new LazyDoubleCheckSingleton();
                    // 1、分配内存给这个对象
                    // 2、初始化对象
                    // 3、设置lazy指向刚分配的内存地址
                }
            }
        }
        return lazy;
    }
}

最终版本,采用静态内部类的方式:

package org.example.spring.designpattern.singleton;

/**
 * TODO 采用静态内部类的懒汉单例模式
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/9 08:52
 */
public class LazyInnerClassSingleton {
    // 使用LazyInnerClassGeneral的时候,默认会先初始化内部类
    // 如果没使用,则内部类是不加载的

    private LazyInnerClassSingleton() {
    }
    // 每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载
    public static final LazyInnerClassSingleton getInstance() {
        // 在返回结果之前,一定会先加载内部类
        return LazyHolder.INSTANCE;
    }
    // 默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }
}

史上最牛的单例模式实现方式【防止反射破坏掉单例】

package org.example.spring.designpattern.singleton;

/**
 * TODO 自认为史上最强的单例模式
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/9 08:58
 */
public class LazyInnerClassSingletonPlus {
    // 使用LazyInnerClassGeneral的时候,默认会先初始化内部类
    // 如果没使用,则内部类是不加载的

    private LazyInnerClassSingletonPlus() {
        if (LazyHolder.INSTANCE != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    // 每一个关键字都不是多余的,static是为了单例的空间共享,保证这个方法不会被重写、重载
    public static final LazyInnerClassSingletonPlus getInstance(){
        return LazyHolder.INSTANCE;
    }

    // 默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingletonPlus INSTANCE = new LazyInnerClassSingletonPlus();
    }
}

线程单例实现ThreadLocal
ThreadLocal不能保证其创建的对象是全局唯一的,但是能保证在单个线程中是唯一的,天生是线程安全的:

package org.example.spring.designpattern.singleton;

/**
 * TODO 线程单例实现ThreadLocal
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/9 09:05
 */
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };
    
    private ThreadLocalSingleton() {
        
    }
    public static ThreadLocalSingleton getInstance() {
        return threadLocalSingleton.get();
    }
}

总结

单例模式可以保证内存里只有一个实例,减少了内存的开销,还可以避免对资源的多重占用。