Java懒汉、饿汉、内部类、枚举各种单例模式

参考链接

其他设计模式见码云

代码在 designPattern包下

1 如何正确地写出单例模式

2 单例模式

3 枚举单例(如果枚举类实例参数有多个,那么枚举单例将失效)

4 单例模式 静态内部类 解决反射得到多个对象

5 【JVM】为什么静态内部类实现单例模式是线程安全?

概念及作用

单例模式是指在内存中只会创建一次对象的设计模式,让所有需要调用的地方共享改对象。避免了程序中多次使用同一对象时频繁创建对象导致内存升高。

类型

懒汉式

说明

当程序使用对象时,判断对象是否实例化,未实例化则实例化改对象,已实例化则直接返回。

懒汉式单例需要私有构造方法,双重校验非空并加锁。

/**
 * 懒汉式单例模式
 * @author linmeng
 * @version 1.0
 * @date 2021年11月9日 21:25
 */
public class LazySingleton {
    private static volatile LazySingleton lazySingleton;

    private LazySingleton(){}

    /**
     *
     * 懒汉式单例:双重校验并加锁,但是new 对象不是原子性操作,还是有可能并发安全问题
     * 
     * @author linmeng
     * @date 2021年11月9日 21:27
     * @return com.sword.www.designPattern.Singleton
     */
    public static LazySingleton getLazyInstance(){
        if (lazySingleton ==null){
            synchronized (LazySingleton.class){
                if (lazySingleton ==null){
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

流程

当多个线程进入时,先判空,这个时候有可能有多个线程进行抢锁,但是只有一个线程抢到锁,另一线程只能等待该线程创建对象,该线程创建完对象直接返回,这时候另一个线程加锁,里面再来一个非空判断,直接让他拿到对象。

volatile关键字作用

jvm创建对象分为三个步骤

  1. 为对象分配空间
  2. 初始化对象
  3. 将初始化的对象指向分配的空间

jvm在执行语句时,为了提高性能,有可能不按照代码顺序执行。这时候,代码执行顺序有可能变成1 3 2.这个时候一个线程走到3那一步,但是没有初始化对象,另一个线程判断非空,对象不为空,直接返回未初始化的对象进行使用,这就会造成空指针。

但是用了volatile关键字,禁止指令重排,就不会出现这种情况。

饿汉式

说明

在程序加载时创建对象,调用时直接返回

/**
 * 饿汉式单例模式
 * @author linmeng
 * @version 1.0
 * @date 2021年11月9日 21:25
 */
public class EagerSingleton {
    private static final EagerSingleton eagerSingleton = new EagerSingleton();

    private EagerSingleton(){}

    /**
     * 
     * 直接返回 
     * @author linmeng
     * @date 2021年11月9日 21:44 
     * @return com.sword.www.designPattern.singleton.EagerSingleton
     */
    public static EagerSingleton getEagerInstance(){
        
        return eagerSingleton;
    }
}

单例模式破坏

反射破坏

反射可以使用私有构造器破坏单例模式

/**
     *
     * 反射破坏单例模式
     * @param args
     * @author linmeng
     * @date 2021年11月9日 21:45
     * @return void
     */
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 获取构造器
        Constructor<EagerSingleton> constructor = EagerSingleton.class.getDeclaredConstructor();
        // 设置可访问私有构造
        constructor.setAccessible(true);
        // 反射创建对象
        EagerSingleton eagerSingleton = constructor.newInstance();
        // 单例创建对象
        EagerSingleton lazyInstance = EagerSingleton.getLazyInstance();
        // 判断对象是否同一个
        System.out.println(lazyInstance==eagerSingleton);

    }

序列化反序列化破坏

序列化反序列化可以破坏单例模式,对象序列化成一个文件,然后反序列化时读取这个文件会生成一个新的对象,导致生成两个对象

 public static void main(String[] args) throws Exception {

        // 创建文件输出流
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("LazySingleton.file"));
        // 写入文件
        outputStream.writeObject(LazySingleton.getLazyInstance());
        // 文件中读取单例对象
        File file = new File("LazySingleton.file");
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
        LazySingleton lazySingleton = (LazySingleton) inputStream.readObject();
        System.out.println(lazySingleton==LazySingleton.getLazyInstance());
    }

静态内部类单例

类中添加一个静态内部类,静态内部类使用饿汉式创建对象

public class StaticNestSingleton {

    private StaticNestSingleton(){}

    private static class SingletonHolder{
        private static final StaticNestSingleton SINGLETON = new StaticNestSingleton();
    }
    /**
     *
     * 直接返回
     * @author linmeng
     * @date 2021年11月9日 21:44
     * @return com.sword.www.designPattern.singleton.EagerSingleton
     */
    public static StaticNestSingleton getStaticNestInstance(){

        return SingletonHolder.SINGLETON;
    }
    public static void main(String[] args) throws Exception{
        Constructor<StaticNestSingleton> constructor = StaticNestSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        StaticNestSingleton staticNestSingleton = constructor.newInstance();
        System.out.println(staticNestSingleton==StaticNestSingleton.getStaticNestInstance());

        // 创建文件输出流
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("StaticNestSingleton.file"));
        // 写入文件
        outputStream.writeObject(StaticNestSingleton.getStaticNestInstance());
        // 文件中读取单例对象
        File file = new File("StaticNestSingleton.file");
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
        StaticNestSingleton staticNestSingleton2 = (StaticNestSingleton) inputStream.readObject();
        System.out.println(staticNestSingleton2==StaticNestSingleton.getStaticNestInstance());
    }
}

相当于懒汉跟饿汉优点集成,类初始化是不会初始化内部类,只有用到的时候才会初始化。为什么线程安全见参考链接。缺点是反射和序列化都能破坏单例。

终极大招(枚举式)

public enum EnumSingleton {
    INSTANCE;
    EnumSingleton() { System.out.println("枚举创建对象了"); }
    public static void main(String[] args) { /* test(); */ }
    public static void main() {
        EnumSingleton t1 = EnumSingleton.INSTANCE;
        EnumSingleton t2 = EnumSingleton.INSTANCE;
        System.out.println(t1 == t2);
    }
}

优劣势见参考链接2

如果枚举类中有多个枚举,多线程会失效。见参考链接3.这个是人为bug,生死有命富贵在天。文章有些草率,后续再优化。

猜你喜欢

转载自blog.csdn.net/qq_36746327/article/details/121238727