设计模式之单例模式

五种实现方式:

方式一:饿汉式
复制代码
//饿汉式
public class Singleton1 implements Serializable {
    private static final Singleton1 SINGLETON_TEST=new Singleton1();
    //构造私有
    private Singleton1() {
        System.out.println("私有构造方法");
    }
    public static Singleton1 getInstance(){
        return SINGLETON_TEST;
    }
    public static void otherMotherd(){
        System.out.println("其他方法");
    }
}

测试:

复制代码
public class SingleTest {
    public static void main(String[] args) {
        Singleton1.otherMotherd();//检测是饿汉式
        //私有构造方法
        //其他方法
        System.out.println(Singleton1.getInstance());//Singleton1@4d7e1886
        System.out.println(Singleton1.getInstance());//Singleton1@4d7e1886
        //以上两个实例为同一个对象
    }
}

结果:

单例被破坏的情况:
序列化和反序列化:

当单例类实现了Serializable接口时,对象被序列化后再反序列化时会创建新的实例。为了避免这种情况,可以通过重写readResolve()方法来返回单例实例。

测试反序列化后的对象和原始对象是否一致:

复制代码
/**
     * 实现序列化接口后可能破坏单例模式
     */
    @Test
    void test5() throws Exception {
        //将对象写入文件中
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("single1.ser"));
        oos.writeObject(Singleton1.getInstance());
        oos.close();
        //从文件中读取对象
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("single1.ser"));
        Singleton1 single = (Singleton1) ois.readObject();
        ois.close();
        System.out.println("original:"+Singleton1.getInstance().hashCode());
        System.out.println("DeSerializable:"+single.hashCode());
    }

结果:

预防:

复制代码
public class Singleton implements Serializable {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }

    protected Object readResolve() {
        return instance;
    }
}
反射机制:

通过反射可以调用类的私有构造方法,从而创建多个实例。为了防止通过反射破坏单例模式,可以在构造方法中添加逻辑判断,确保只创建一个实例。

反射破坏单例:

复制代码
@Test
    void test() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Singleton1 instance = Singleton1.getInstance();
        Constructor<? extends Singleton1> constructor = instance.getClass().getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton1 singleton1 = constructor.newInstance();
        System.out.println(instance==singleton1);
    }

结果:

预防:在构造方法处进行判断

复制代码
//饿汉式
public class Singleton2 implements Serializable {
    private static final Singleton2 SINGLETON_TEST=new Singleton2();
    //构造私有
    private Singleton2() {
        if (SINGLETON_TEST!=null){
            throw new IllegalStateException("对象已经创建");
        }
        System.out.println("私有构造方法");
    }
    public static Singleton2 getInstance(){

        return SINGLETON_TEST;
    }

    public static void otherMotherd(){
        System.out.println("其他方法");
    }
}

以上防止反射破坏单例测试:

复制代码
/**
     *防止单例被破坏
     */
    @Test
    void test3() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Singleton2 instance = Singleton2.getInstance();
        Constructor<? extends Singleton2> constructor = instance.getClass().getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton2 singleton2 = constructor.newInstance();
        System.out.println(instance==singleton2);
    }

结果:

类加载器:

如果使用不同的类加载器加载同一个类,也可能导致创建多个实例。为了解决这个问题,可以在获取实例时指定类加载器,确保只有一个实例被创建。

测试使用不同的类加载器导致同一个类创建多个实例的情况:

复制代码
 public static void main(String[] args) throws Exception {
        CurstomLoader loader1=new CurstomLoader();
        CurstomLoader loader2=new CurstomLoader();
        Class<?> single1 = loader1.loadClass("com.example.singletonmodle.single.Singleton1");
        Class<?> single2 = loader2.loadClass("com.example.singletonmodle.single.Singleton1");
        Singleton1 instance1 = (Singleton1) single1.newInstance();
        Singleton1 instance2= (Singleton1) single2.newInstance();
        System.out.println("loader1:"+instance1.hashCode());
        System.out.println("loader2:"+instance2.hashCode());
    }
复制代码
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance(ClassLoader classLoader) {
        synchronized (Singleton.class) {
            if (instance == null) {
                try {
                    Class<?> clazz = classLoader.loadClass(Singleton.class.getName());
                    instance = (Singleton) clazz.newInstance();
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return instance;
    }
}
  1. 通过unsafe破坏单例:目前没有预防的方法。

总结:多线程破坏单例使用DCL+voliatle预防,反射破坏单例使用在构造方法处检查对象是否存在进行预防,实现序列化接口破坏单例使用实现readResolve方法,返回已经创建的单例对象。unsafe目前还没有预防的方式。

方式二:枚举类
复制代码
public enum Singleton3 {
    INSTANCE;
    private Singleton3(){
        System.out.println("枚举类的私有构造方法");
    }

    @Override
    public String toString() {
        //getClass().getName()返回该对象的类名,Integer.toHexString(hashCode())返回该对象的哈希码的十六进制表示。
        return getClass().getName()+"@"+Integer.toHexString(hashCode());
    }

    public Singleton3 getInstance(){
        return INSTANCE;
    }
    public void otherMethod(){
        System.out.println("其他方法");
    }

}

测试:

枚举实现的单例可以预防反序列化破坏单例

枚举也可以预防反射破坏单例

枚举类的构造方法不是无参构造,有两个参数。

但是枚举无法预防unsafe破坏单例模式。

方式三:懒汉式
复制代码
//懒汉式
public class Singleton4 {
    private static  Singleton4 SINGLETON_TEST;
    //构造私有
    private Singleton4() {
        System.out.println("私有构造方法");
    }
    public static Singleton4 getInstance(){
if (SINGLETON_TEST==null){
            SINGLETON_TEST=new Singleton4();
        }
        return SINGLETON_TEST;
    }
    public static void otherMotherd(){
        System.out.println("其他方法");
    }
}

测试:

以上单例模式在多线程下存在单例被破坏的可能。

多线程环境下未处理好并发访问:

如果在多线程环境下,多个线程同时尝试获取单例实例,可能会导致创建多个实例的情况。可以使用DCL(双重检测来确保只有一个实例被创建,DCL需要配合volatile使用,确保可见性)。懒汉式

复制代码
public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建多个线程同时获取单例实例
        Thread thread1 = new Thread(() -> {
            Singleton singleton = Singleton.getInstance();
            System.out.println("Thread 1: " + singleton.hashCode());
        });

        Thread thread2 = new Thread(() -> {
            Singleton singleton = Singleton.getInstance();
            System.out.println("Thread 2: " + singleton.hashCode());
        });

        thread1.start();
        thread2.start();
    }
}

预防:DCL+voliatle ,也就是第四种实现方式。

方式四:DCL+voliatle
复制代码
public class Singleton {
    private static volatile Singleton instance; //保证共享变量的有序性

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

方式五:静态内部类,懒汉式:

复制代码
/静态内部类方式
public class Singleton6 {
    private static  Singleton6 SINGLETON_TEST=null;
    //构造私有
    private Singleton6() {

        System.out.println("私有构造方法");
    }
    private static class Holder{
        static Singleton6 INSTANCE=new Singleton6();
    }
    public static Singleton6 getInstance(){
        return Holder.INSTANCE;
    }
    public static void otherMotherd() {
        System.out.println("其他方法");
    }
}

JDK中单例的体现方式:

单例模式一般在jdk的一些库中见到,自己不要乱用单例模式,很容易用错。

相关推荐
桦说编程7 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
lifallen7 小时前
Java Stream sort算子实现:SortedOps
java·开发语言
IT毕设实战小研7 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
快乐的划水a7 小时前
组合模式及优化
c++·设计模式·组合模式
没有bug.的程序员8 小时前
JVM 总览与运行原理:深入Java虚拟机的核心引擎
java·jvm·python·虚拟机
甄超锋8 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
阿华的代码王国8 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Zyy~8 小时前
《设计模式》装饰模式
java·设计模式
A尘埃9 小时前
企业级Java项目和大模型结合场景(智能客服系统:电商、金融、政务、企业)
java·金融·政务·智能客服系统
青云交9 小时前
Java 大视界 -- 基于 Java 的大数据可视化在城市交通拥堵治理与出行效率提升中的应用(398)
java·大数据·flink·大数据可视化·拥堵预测·城市交通治理·实时热力图