JAVA单例模式代码实现

本文探讨JAVA中的单例模式,包括懒汉模式、静态内部类单例、饿汉模式和枚举单例,详细解析了各模式的优缺点及多线程安全性。并深入分析了单例模式可能遇到的反射攻击和序列化问题,提出了解决方案。
摘要由CSDN通过智能技术生成

JAVA常见的设计模式之单例模式

  • 懒汉模式

             懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间(搬运工)。

标准的懒汉模式

class LazySingleton {
    // 私有成员属性
    private LazySingleton lazySingleton;
    
    // 私有构造方法
    private LazySingleton() {
    }
    
    // 公共的获取实例方法
    public LazySingleton getLazySingleton() {
        // 如果成员属性为空,则创建实例
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

单线程环境下,该单例模式只会有一个实例

public class TestDemo
{
    public static void main(String[] args) {
        LazySingleton lazySingleton = LazySingleton.getLazySingleton();
        LazySingleton lazySingleton2 = LazySingleton.getLazySingleton();
        System.out.println(lazySingleton == lazySingleton2);
    }
}

运行结果:

多线程模式下,可能会产生多个实例

public class TestDemo
{
    public static void main(String[] args) {
        new Thread(() -> {
            LazySingleton lazySingleton = LazySingleton.getLazySingleton();
            System.out.println(lazySingleton);
        }).start();
        new Thread(() -> {
            LazySingleton lazySingleton = LazySingleton.getLazySingleton();
            System.out.println(lazySingleton);
        }).start();
    }
}

运行结果:

初步改进

class LazySingleton {
    // 私有成员属性
    private static LazySingleton lazySingleton;

    // 私有构造方法
    private LazySingleton() {
    }

    // 公共的获取实例方法
    public synchronized static LazySingleton getLazySingleton() {
        // 如果成员属性为空,则创建实例
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

缺点,每次调用方法都会加锁,效率低

再次改进

class LazySingleton {
    // 私有成员属性,使用volatile可以保证代码的有序性,防止指令重排
    private volatile static LazySingleton lazySingleton;

    // 私有构造方法
    private LazySingleton() {
    }

    // 公共的获取实例方法
    // 使用synchronized + 双重确认机制可以保证线程安全,但有可能存在指令重排,会导致创建多个实例
    public static LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            synchronized (LazySingleton.class) {
                if (lazySingleton == null) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

静态类部类单例

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

 静态类不类单例不会有线程安全问题,线程安全由类加载机制担保

恶汉模式

             饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间(搬运工)。 

// 利用类加载机制保证线程安全
class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getHungrySingleton() {
        return hungrySingleton;
    }
}

 枚举单例模式

package com.hy.test.singletonDemo;

public enum EnumSingletonDemo {
    INSTANCE;

}

class EnumTest {
    public static void main(String[] args) {
        EnumSingletonDemo instance = EnumSingletonDemo.INSTANCE;
        EnumSingletonDemo instance2 = EnumSingletonDemo.INSTANCE;
        System.out.println(instance == instance2);
    }
}

运行结果:

单例模式可能出现的问题(都会用静态类不类单例举例)

反射攻击

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        Class clazz = InnerSingleton.class;
        Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

运行结果:

由此可见,反射生成了一个新的对象,不符合单例模式的定义

解决方法:在私有构造器中添加判断,如果已存在实例对象,抛出异常(也可进行其他操作,根据需求决定)

优化后的代码如下

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        Class clazz = InnerSingleton.class;
        Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类部类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例模式已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

 运行结果:

注意:只有恶汉模式与静态类部类能防止反射攻击

序列化相关问题

 首先,我们对创建的实例进行序列化,代码如下:

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws IOException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        // 序列化测试
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));
        oos.writeObject(innerSingleton);
        oos.close();
        // 反序列化
/*        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));
        InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();
        System.out.println(innerSingleton == innerSingleton1);*/
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    // 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,
    // 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败
    private static final long serialVersionUID = 7822769557659839582L;

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类不类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }
    
}

然后,我们进行反序列化,查看反序列化生成的实例跟单例的实例是否是同一个

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        // 序列化测试
/*        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));
        oos.writeObject(innerSingleton);
        oos.close();*/
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));
        InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    // 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,
    // 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败
    private static final long serialVersionUID = 7822769557659839582L;

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类不类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

运行结果:

由此可见,反序列化创建了一个新的实例

解决方法:Serializable的源码中给出了提示

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        // 序列化测试
/*        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));
        oos.writeObject(innerSingleton);
        oos.close();*/
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));
        InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    // 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,
    // 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败
    private static final long serialVersionUID = 7822769557659839582L;

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类不类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

    // 反序列化时,如果是单例模式,需要重写该方法,返回单例的实例,否则会获取到不同的对象
    Object readResolve() throws ObjectStreamException {
        return InnerSingletonHolder.innerSingleton;
    }
}

运行结果:

因此,在工作中推荐大家使用静态类部类单例模式,可以有效的防止反射攻击与序列化带来的相关问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值