03-JAVA设计模式-单例模式详解

news2024/11/18 16:53:15

单例模式

单例模式(Singleton Pattern)是设计模式中的一种,它确保一个类仅有一个实例,并提供一个全局访问点来访问该实例。这种设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
单例模式的应用场景十分广泛,主要涉及需要频繁使用某个对象而又不想重复创建的情况:

常见应用场景:

  • Windows的任务管理器和回收站:这两个都是只能打开一个实例的应用程序,符合单例模式的特点。
  • 网站的计数器:由于需要同步访问,使用单例模式可以确保计数的准确性。
  • 应用程序的日志应用:一般系统共用一个日志,因此采用单例模式可以方便地对日志进行管理和操作。
  • 数据库连接池:数据库连接是一种昂贵的资源,频繁地创建和关闭数据库连接会带来巨大的性能损耗。采用单例模式,可以维护一个数据库连接的实例,从而避免这种损耗。
  • 多线程的线程池:线程池需要方便地对池中的线程进行控制,使用单例模式可以确保线程池的唯一性,从而方便管理。

单例模式的优点

  1. 节省系统资源:由于单例模式限制了一个类只能有一个实例,这避免了因重复创建对象而浪费的内存和其他系统资源。特别是在处理大型对象或需要消耗大量资源的对象时,单例模式可以显著减少资源消耗。
  2. 提高系统性能:由于无需反复创建对象,单例模式减少了在对象创建和销毁过程中的系统开销。在频繁创建和销毁对象会导致性能问题的场景下,单例模式能有效提升性能。
  3. 简化管理:单例模式提供了一个全局访问点,使得开发者可以方便地访问和操作该类的唯一实例。这简化了对对象的管理和维护,降低了代码的复杂性。
  4. 保证数据一致性:在某些情况下,如配置信息、线程池、数据库连接等,需要确保在整个系统中只有一个实例存在,以保证数据的一致性。单例模式可以确保这些资源在全局范围内只有一个访问点,从而避免了数据不一致的问题。
  5. 易于扩展:虽然单例模式限制了类的实例数量,但它并不限制类的功能扩展。开发者可以在保持单例特性的同时,为类添加新的方法和属性,以满足不断变化的需求。

常见五种单例模式实现方式

1. 饿汉式

实现方式SingletonDemo.java

package demo1;

/**
 *  单例模式:饿汉式
 *  1 提供一个私有静态的SingletonDemo类型属性
 *  2 构造器私有化
 *  3 提供一个public静态的初始化方法getInstance()方法
 *
 * @author Anna.
 * @date 2024/4/5 20:34
 */
public class SingletonDemo {

    // 静态的SingletonDemo类型属性,初始化时,立即加载这个对象
    // 由于JVM类加载规则,加载class时进行初始化,保证了线程安全,但是没有延时加载的优势
    // 因此可能会造成内存浪费,因为无论是否使用到该实例,它都会在类加载时就被创建
    private static SingletonDemo instance = new SingletonDemo();

    // 构造器私有化
    private SingletonDemo() {
    }

    // 提供一个public的初始化方法 getInstance()方法
    public static SingletonDemo getInstance(){
        return instance;
    }
}

单线程测试:

package demo1;

public class SingleThreadedTestClient {
    public static void main(String[] args) {
        // 单线程模式
        SingletonDemo instance1 = SingletonDemo.getInstance();
        SingletonDemo instance2 = SingletonDemo.getInstance();
        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
    }
}

执行结果:

在这里插入图片描述

多线程测试:

package demo1;

public class MultithreadingTestClient {
    public static void main(String[] args) {
        // 多线程模式
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                SingletonDemo instance3 = SingletonDemo.getInstance();
                SingletonDemo instance4 = SingletonDemo.getInstance();

                if (instance3.hashCode() != instance4.hashCode()) {
                    System.out.printf("多线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象-线程名称%s:%s%n",
                            Thread.currentThread().getName(), instance3.hashCode() == instance4.hashCode());
                }

            }, "线程" + i).start();
        }
    }
}

执行结果:

在这里插入图片描述

结论:

优点:没有线程安全问题,实现简单。

缺点:可能会造成内存浪费,因为无论是否使用到该实例,它都会在类加载时就被创建。

2. 懒汉式:

实现方式SingletonDemo.java

package demo2;

/**
 * 单例模式:懒汉式
 * 1 提供一个私有的静态SingletonDemo属性
 * 2 构造函数私有化
 * 3 提供一个public静态的初始化方法getInstance()方法
 *
 * @author Anna.
 * @date 2024/4/5 20:59
 */
public class SingletonDemo {

    // 1 提供一个私有的静态SingletonDemo属性
    // 这里在加载时,不进行初始化,调用getInstance()方法时进行初始化。以达到延时加载的目的
    private static SingletonDemo instance;

    // 2 构造函数私有化
    private SingletonDemo(){}

    // 3 提供一个public静态的初始化方法getInstance()方法
    public static SingletonDemo getInstance(){
        // 判断属性是否为空
        if(instance == null){
            // 延时加载,初始化对象
            instance = new SingletonDemo();
        }
        // 返回对象
        return instance;
    }
}

单线程测试:

package demo2;

public class SingleThreadedTestClient {
    public static void main(String[] args) throws InterruptedException {
        // 单线程模式
        System.out.println("========== 单线程测试-懒汉式单例模式是否线程安全 ==========");
        SingletonDemo instance1 = SingletonDemo.getInstance();
        SingletonDemo instance2 = SingletonDemo.getInstance();
        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
    }
}

执行结果:

在这里插入图片描述

多线程测试:

package demo2;

public class MultithreadingTestClient {
    public static void main(String[] args) {
        // 多线程模式
        System.out.println("========== 多线程测试-懒汉式单例模式是否线程安全 ==========");
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                SingletonDemo instance3 = SingletonDemo.getInstance();
                SingletonDemo instance4 = SingletonDemo.getInstance();

                if (instance3.hashCode() != instance4.hashCode()) {
                    System.out.printf("多线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象-%s:%s%n",
                            Thread.currentThread().getName(), instance3.hashCode() == instance4.hashCode());
                }

            }, "线程" + i).start();
        }
    }
}

执行结果:多线程模式下,多执行几次我们会发现,有时懒汉式单例模式,拿到的并不是同一个对象

在这里插入图片描述

优点:延时加载,节省内存。

缺点:在多线程环境下存在线程安全问题,需要通过加锁来保证实例的唯一性,这可能会影响并发性能。

// 加锁
public static synchronized SingletonDemo getInstance(){
    // 判断属性是否为空
    if(instance == null){
        // 延时加载,初始化对象
        instance = new SingletonDemo();
    }
    // 返回对象
    return instance;
}

3. 双重检锁方式:

实现方式SingletonDemo.java

package demo3;

/**
 * 单例模式:双重检锁方式
 * 1 提供一个私有的静态SingletonDemo属性
 * 2 构造函数私有化
 * 3 提供一个public静态的初始化方法getInstance()方法
 *
 * @author Anna.
 * @date 2024/4/5 20:59
 */
public class SingletonDemo {

    // 1 提供一个私有的静态SingletonDemo属性
    // 这里在加载时,不进行初始化,调用getInstance()方法时进行初始化。以达到延时加载的目的
    // 使用volatile修饰instance,保证多线程环境下instance的可见性
    private volatile static SingletonDemo instance;

    // 2 构造函数私有化
    private SingletonDemo() {
    }

    // 3 提供一个public静态的初始化方法getInstance()方法
    public static SingletonDemo getInstance() {
        // 判断属性是否为空
        if (instance == null) {
            // 使用 synchronized 代码块 进行加锁
            synchronized (SingletonDemo.class) {
                if (instance == null) {
                    // 延时加载,初始化对象
                    instance = new SingletonDemo();
                }
            }

        }
        // 返回对象
        return instance;
    }
}

单线程测试:

package demo3;

public class SingleThreadedTestClient {
    public static void main(String[] args) throws InterruptedException {
        // 单线程模式·
        System.out.println("========== 单线程测试-双重检锁方式单例模式是否线程安全 ==========");
        SingletonDemo instance1 = SingletonDemo.getInstance();
        SingletonDemo instance2 = SingletonDemo.getInstance();
        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
    }
}

执行结果:

在这里插入图片描述

多线程测试:

package demo3;

public class MultithreadingTestClient {
    public static void main(String[] args) {
        // 多线程模式
        System.out.println("========== 多线程测试-双重检锁方式单例模式是否线程安全 ==========");
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                SingletonDemo instance3 = SingletonDemo.getInstance();
                SingletonDemo instance4 = SingletonDemo.getInstance();

                if (instance3.hashCode() != instance4.hashCode()) {
                    System.out.printf("多线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象-%s:%s%n",
                            Thread.currentThread().getName(), instance3.hashCode() == instance4.hashCode());
                }

            }, "线程" + i).start();
        }
    }
}

执行结果:

在这里插入图片描述

优点:结合了饿汉式和懒汉式的优点,既实现了懒加载,又解决了线程安全问题。

缺点:双重检锁的实现虽然巧妙,但也比较复杂,容易出现错误。同时,从Java 5开始,JVM内部对synchronized的实现做了优化,使得这种方式的性能优势不再明显。因此不建议使用

4. 静态内部类式:

实现方式SingletonDemo.java

package demo4;

/**
 * 单例模式:静态内部类方式
 * 
 * 1 定义一个静态内部类,内部类中提供一个静态final的单例对象属性
 * 2 构造函数私有化
 * 3 提供一个public静态的初始化方法getInstance()方法
 *
 * @author Anna.
 * @date 2024/4/5 21:31
 */
public class SingletonDemo {

    // 1 定义一个静态内部类,内部类中提供一个静态final的单例对象属性
    static class SingletonDemoInstance{
        // 静态内部类 利用 JVM类加载器 加载类时 首先初始化 类中静态常量到常量池中且一个class只会被初始化一次这一特性保证线程安全
        private static final SingletonDemo instance = new SingletonDemo();
    }

    // 2 构造函数私有化
    private SingletonDemo() {
    }

    // 3 提供一个public静态的初始化方法getInstance()方法
    public static SingletonDemo getInstance() {
        return SingletonDemoInstance.instance;
    }
}

单线程测试:

package demo4;

public class SingleThreadedTestClient {
    public static void main(String[] args) throws InterruptedException {
        // 单线程模式·
        System.out.println("========== 单线程测试-静态内部类方式单例模式是否线程安全 ==========");
        SingletonDemo instance1 = SingletonDemo.getInstance();
        SingletonDemo instance2 = SingletonDemo.getInstance();
        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
    }
}

执行结果:

在这里插入图片描述

多线程测试:

package demo4;

public class MultithreadingTestClient {
    public static void main(String[] args) {
        // 多线程模式
        System.out.println("========== 多线程测试-静态内部类方式单例模式是否线程安全 ==========");
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                SingletonDemo instance3 = SingletonDemo.getInstance();
                SingletonDemo instance4 = SingletonDemo.getInstance();

                if (instance3.hashCode() != instance4.hashCode()) {
                    System.out.printf("多线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象-%s:%s%n",
                            Thread.currentThread().getName(), instance3.hashCode() == instance4.hashCode());
                }

            }, "线程" + i).start();
        }
    }
}

执行结果:

在这里插入图片描述

优点:线程安全,实现简单,且实现了懒加载,延时加载。

缺点:相较于双重检查锁定式,其实现方式稍微复杂一些。

5. 枚举式:

实现方式SingletonDemo.java

package demo5;

/**
 * 单例模式:枚举
 *
 * 1 定义一个枚举的元素
 * 2 枚举本省就是单例模式。有JVM从根本上提供保证。避免通过反射和反序列化的漏洞
 * 3 如需使用提供相应public 方法即可
 *
 * @author Anna.
 * @date 2024/4/5 21:31
 */
public enum SingletonDemo {
    /** 定义一个枚举元素,它就代表了单例的一个实例*/
    INSTANCD;

    public void test(){System.out.println("枚举单例内部方法调用了");}
}

单线程测试:

package demo5;

public class SingleThreadedTestClient {
    public static void main(String[] args) throws InterruptedException {
        // 单线程模式·
        System.out.println("========== 单线程测试枚举单例模式是否线程安全 ==========");
        SingletonDemo instance1 = SingletonDemo.INSTANCD;
        SingletonDemo instance2 = SingletonDemo.INSTANCD;
        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
        
        // 调用方式
        SingletonDemo.INSTANCD.test();
    }
}

执行结果:

在这里插入图片描述

多线程测试:

package demo5;

public class MultithreadingTestClient {
    public static void main(String[] args) {
        // 多线程模式
        System.out.println("========== 多线程测试枚举单例模式是否线程安全 ==========");
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                SingletonDemo instance3 = SingletonDemo.INSTANCD;
                SingletonDemo instance4 = SingletonDemo.INSTANCD;

                if (instance3.hashCode() != instance4.hashCode()) {
                    System.out.printf("多线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象-%s:%s%n",
                            Thread.currentThread().getName(), instance3.hashCode() == instance4.hashCode());
                }

            }, "线程" + i).start();
        }
    }
}

执行结果:

在这里插入图片描述

优点:线程安全,实现简单,防止反射和反序列化漏洞。

缺点:没有实现懒加载,类加载时就创建了对象实例。

当然通过Spring,也可以创建单例模式(后面介绍)

多线程模式下效率测试

package demo6;

import demo1.SingletonDemo;

import java.util.concurrent.CountDownLatch;

/**
 * 测试5中单例模式 多线程下的执行效率
 *
 * @author Anna.
 * @date 2024/4/5 21:53
 */
public class TestClient {
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        int threadNum = 10;
        CountDownLatch countDownLatch1 = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000000000; j++) {
                    SingletonDemo instance = SingletonDemo.getInstance();
                }
                countDownLatch1.countDown();
            }).start();
        }
        countDownLatch1.await();
        long end = System.currentTimeMillis();
        System.out.printf("饿汉式-%s个多线程各自创建1000000000次-耗时:%s%n", threadNum, end - start);

        CountDownLatch countDownLatch2 = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000000000; j++) {
                    demo2.SingletonDemo instance = demo2.SingletonDemo.getInstance();
                }
                countDownLatch2.countDown();
            }).start();
        }
        countDownLatch2.await();
        end = System.currentTimeMillis();
        System.out.printf("懒汉式-%s个多线程各自创建1000000000次-耗时:%s%n", threadNum, end - start);

        CountDownLatch countDownLatch3 = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000000000; j++) {
                    demo2.SingletonDemo instance = demo2.SingletonDemo.getInstance();
                }
                countDownLatch3.countDown();
            }).start();
        }
        countDownLatch3.await();
        end = System.currentTimeMillis();
        System.out.printf("双重检锁方式-%s个多线程各自创建1000000000次-耗时:%s%n", threadNum, end - start);

        CountDownLatch countDownLatch4 = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000000000; j++) {
                    demo2.SingletonDemo instance = demo2.SingletonDemo.getInstance();
                }
                countDownLatch4.countDown();
            }).start();
        }
        countDownLatch4.await();
        end = System.currentTimeMillis();
        System.out.printf("静态内部类方式-%s个多线程各自创建1000000000次-耗时:%s%n", threadNum, end - start);

        CountDownLatch countDownLatch5 = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000000000; j++) {
                    demo2.SingletonDemo instance = demo2.SingletonDemo.getInstance();
                }
                countDownLatch5.countDown();
            }).start();
        }
        countDownLatch5.await();
        end = System.currentTimeMillis();
        System.out.printf("枚举单例-%s个多线程各自创建1000000000次-耗时:%s%n", threadNum, end - start);
    }
}

执行结果

在这里插入图片描述

如何选用

  • 单例对象占用资源少,不需要延时加载:枚举式 好于 饿汉式
  • 单例对象暂用资源大,需要延时加载:静态内部类 好于 懒汉式

通过反射方式破坏单例模式

验证

package demo7;

import demo4.SingletonDemo;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 通过反射破坏单例模式
 *
 * @author Anna.
 * @date 2024/4/5 22:07
 */
public class TestClient {

    public static void main(String[] args) throws Exception {
        // 静态内部内实现单例模式
        SingletonDemo instance1 = SingletonDemo.getInstance();
        SingletonDemo instance2 = SingletonDemo.getInstance();

        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
        
        // 反射获取静态内部内实现单例模式
        Class<SingletonDemo> clazz = (Class<SingletonDemo>) Class.forName("demo4.SingletonDemo");
        // 获取构造器
        Constructor<SingletonDemo> declaredConstructor = clazz.getDeclaredConstructor(null);
        // 设置跳过安全检查
        declaredConstructor.setAccessible(true);
        // 初始化对象
        SingletonDemo singletonDemo1 = declaredConstructor.newInstance();
        SingletonDemo singletonDemo2 = declaredConstructor.newInstance();

        System.out.printf("反射方式,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", singletonDemo1.hashCode() == singletonDemo2.hashCode());
    }
}

执行结果:

在这里插入图片描述

优化方案

SingletonDemo.java

package demo7;

public class SingletonDemo {

    // 1 定义一个静态内部类,内部类中提供一个静态final的单例对象属性
    static class SingletonDemoInstance{
        // 静态内部类 利用 JVM类加载器 加载类时 首先初始化 类中静态常量到常量池中且一个class只会被初始化一次这一特性保证线程安全
        private static final SingletonDemo instance = new SingletonDemo();
    }

    // 2 构造函数私有化
    private SingletonDemo() {
        // 反射通过调用SingletonDemo()进行初始化时,判断属性 SingletonDemoInstance.instance 是否为空,如果为空则直接抛出异常
        if(SingletonDemoInstance.instance != null){
            throw new RuntimeException();
        }
    }

    // 3 提供一个public静态的初始化方法getInstance()方法
    public static SingletonDemo getInstance() {
        return SingletonDemoInstance.instance;
    }
}

TestClient2.java

package demo7;

import java.lang.reflect.Constructor;

public class TestClient2 {

    public static void main(String[] args) throws Exception {
        // 静态内部内实现单例模式
        SingletonDemo instance1 = SingletonDemo.getInstance();
        SingletonDemo instance2 = SingletonDemo.getInstance();

        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
        
        // 反射获取静态内部内实现单例模式
        Class<SingletonDemo> clazz = (Class<SingletonDemo>) Class.forName("demo7.SingletonDemo");
        // 获取构造器
        Constructor<SingletonDemo> declaredConstructor = clazz.getDeclaredConstructor(null);
        // 设置跳过安全检查
        declaredConstructor.setAccessible(true);
        // 初始化对象
        SingletonDemo singletonDemo1 = declaredConstructor.newInstance();
        SingletonDemo singletonDemo2 = declaredConstructor.newInstance();

        System.out.printf("反射方式,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", singletonDemo1.hashCode() == singletonDemo2.hashCode());
    }
}

执行结果:

在这里插入图片描述

通过反序列化方式破坏单例模式

序列化时java,必须实现Serializable接口,因此修改单例
SingletonDemo.java

package demo8;

import java.io.Serializable;

// 实现Serializable接口
public class SingletonDemo implements Serializable {

    // 序列化ID,用于版本控制  
    private static final long serialVersionUID = 1L;

    // 1 定义一个静态内部类,内部类中提供一个静态final的单例对象属性
    static class SingletonDemoInstance{
        // 静态内部类 利用 JVM类加载器 加载类时 首先初始化 类中静态常量到常量池中且一个class只会被初始化一次这一特性保证线程安全
        private static final SingletonDemo instance = new SingletonDemo();
    }

    // 2 构造函数私有化
    private SingletonDemo() {
    }

    // 3 提供一个public静态的初始化方法getInstance()方法
    public static SingletonDemo getInstance() {
        return SingletonDemoInstance.instance;
    }
}

TestClient.java

package demo8;

import demo8.SingletonDemo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 通过反序列化单例模式
 *
 * @author Anna.
 * @date 2024/4/5 22:07
 */
public class TestClient {

    public static void main(String[] args) throws Exception {
        // 静态内部内实现单例模式
        SingletonDemo instance1 = SingletonDemo.getInstance();
        SingletonDemo instance2 = SingletonDemo.getInstance();

        System.out.printf("单线程模式下,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance2.hashCode());
        
        // 序列化instance1到本地文件中 序列化时必须实现序列化接口
        try(FileOutputStream fos = new FileOutputStream("D:/temp/a.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos);){
            oos.writeObject(instance1);
        }

        // 反序列化获取instance1对象
        try(FileInputStream fis = new FileInputStream("D:/temp/a.txt"); ObjectInputStream ois = new ObjectInputStream(fis);){
            SingletonDemo instance3 = (SingletonDemo) ois.readObject();
            System.out.printf("反序列化方式,判断获取的两个单例模式对象,判断是否是同一个对象:%s%n", instance1.hashCode() == instance3.hashCode());
        }
    }
}

执行结果:

在这里插入图片描述

优化方案

为了避免当通过反序列化一个单例类的实例时,可能会创建该类的另一个实例,从而破坏单例模式的唯一性保证。

可以通过在单例类中实现readResolve方法来确保反序列化时返回的是单例的原始实例。

readResolve方法是java.io.Serializable接口中的一个特殊方法, 当反序列化对象时,如果对象所属的类定义了readResolve方法, JVM会调用这个方法,并返回其返回的对象,而不是新创建的对象。

因此,我们可以在单例类的readResolve方法中返回单例的原始实例,以确保反序列化不会创建新的实例。

package demo8;

import java.io.ObjectStreamException;
import java.io.Serializable;

// 实现Serializable接口
public class SingletonDemo implements Serializable {

    // 序列化ID,用于版本控制
    private static final long serialVersionUID = 1L;

    // 1 定义一个静态内部类,内部类中提供一个静态final的单例对象属性
    static class SingletonDemoInstance{
        // 静态内部类 利用 JVM类加载器 加载类时 首先初始化 类中静态常量到常量池中且一个class只会被初始化一次这一特性保证线程安全
        private static final SingletonDemo instance = new SingletonDemo();
    }

    // 2 构造函数私有化
    private SingletonDemo() {
    }

    // 3 提供一个public静态的初始化方法getInstance()方法
    public static SingletonDemo getInstance() {
        return SingletonDemoInstance.instance;
    }

    // readResolve方法是java.io.Serializable接口中的一个特殊方法,
    // 当反序列化对象时,如果对象所属的类定义了readResolve方法,
    // JVM会调用这个方法,并返回其返回的对象,而不是新创建的对象。
    // 因此,我们可以在单例类的readResolve方法中返回单例的原始实例,以确保反序列化不会创建新的实例。
    protected Object readResolve() throws ObjectStreamException {
        return getInstance();
    }
}

执行结果:

在这里插入图片描述

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1570961.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

专题三——二分算法

目录 原理 模板 朴素二分算法 非朴素二分算法 一二分查找 二在排序数组中查找元素的第一个和最后一个位置 三点名 四x的平方根 五搜索插入位置 六山脉数组的峰顶索引 七寻找峰值 八寻找旋转排序数组中的最小值 原理 定义两个指针&#xff1a;left指向数组第一个元…

实景三维在文化旅游领域的应用

实景三维技术&#xff0c;作为一种前沿的科技手段&#xff0c;近年来在文化旅游领域的应用逐渐崭露头角。它能够将真实世界的场景以三维的形式精确呈现&#xff0c;为游客带来身临其境的体验&#xff0c;为文化旅游注入新的活力。本文将探讨实景三维在文化旅游领域的应用及其所…

编程新手必看,学习python中数字数据类型内容(7)

Python中的数字数据类型主要包括以下几种&#xff1a; 整数&#xff08;int&#xff09;&#xff1a;这是最基本的数字类型&#xff0c;用于表示整数值&#xff0c;如1、-5和1000等。在Python中&#xff0c;整数可以处理任意大小的数值&#xff0c;并且支持多种数学运算。 浮点…

Windows与Linux路径分隔符对比及Java代码实战

在Windows中&#xff0c;磁盘中用反斜杠&#xff08;又称为右斜杠&#xff09;\表示路径的分隔。在浏览器中用正斜杠/来表示路径的分隔。 Linux则是统一用/表示路径的分隔的。下面给出Linux中一些常见的路径表示&#xff1a; / 表示根目录./ 表示当前目录…/ 表示上级目录 …

【C++】C++中的stack和queue

一、概述 本篇blog写明了介绍的是STL(标准模板库)中的stack和queue&#xff0c;栈和队列虽然在处理数据的方式上有明显的不同&#xff0c;但它们作为操作受限的线性数据结构&#xff0c;在学习和应用中经常被放在一起讨论&#xff0c;以便更全面地理解数据结构的概念和使用。 在…

Windows瘦客户机专用系统安装教程

前言 小白偶然看到微软有给瘦客户机定制了专用系统Windows Thin PC x86。 从名字就可以看出来&#xff0c;瘦客户机的系统是32位的&#xff0c;安装完系统之后可以用来干啥&#xff0c;咱们很多小伙伴估计都不清楚。 首先要了解&#xff1a;什么是瘦客户机&#xff1f; 瘦客户…

逐步学习Go-WaitGroup【连字都懒得写了,直接Show my Code】

package waitgroup_testimport ("fmt""runtime""sync""testing""time""github.com/stretchr/testify/assert" )// 这是对Go语标准库中sync包下的WaitGroup的描述。// WaitGroup用于等待一组并发的goroutine结结束…

争相上市,黑芝麻智能和地平线,能突出重围吗?

图片&#xff5c;freeflo.ai ©自象限原创 作者丨罗辑 中国最有代表性两家自动驾驶大算力芯片&#xff08;SoC&#xff09;公司在港交所相遇了。 3月23日&#xff0c;黑芝麻智能向港交所递交主板上市申请&#xff1b;3天之后&#xff0c;地平线也向港交所递交了招股书。…

基于Springboot的少儿编程管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的少儿编程管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

二极管特性介绍

二极管 贴片二极管 不同封装的二极管 二极管不同的符号 二极管的结构组成 二极管就是由一个PN结加上相应的电极引线及管壳封装而成的。 采用不同的掺杂工艺&#xff0c;通过扩散作用&#xff0c;将P型半导体与N型半导体制作在同一块半导体&#xff08;通常是硅或锗&#xff…

【前端面试3+1】08 css选择器、在前端页面展示后端传来的图片数组、请求方法的常见类型、【搜索插入位置】

一、css选择器有哪些&#xff1f; 1.元素选择器&#xff1a; 通过元素名称选择元素。 示例&#xff1a;p 选择所有段落元素。 2.类选择器&#xff1a; 通过类名选择元素。 示例&#xff1a;.btn 选择所有类名为 btn 的元素。 3.ID选择器&#xff1a; 通过id属性选择元素。 示例…

Adobe Bridge 2024:连接创意,探索无限可能 mac/win版

Adobe Bridge 2024&#xff0c;作为Adobe家族中的一款强大的创意管理工具&#xff0c;再次革新了数字资产管理和工作流程优化的标准。这款软件不仅继承了Adobe Bridge一贯的直观界面和强大功能&#xff0c;更在多个方面进行了突破性的改进。 Bridge 2024软件获取 全面的资源管…

软考之零碎片段记录(六)+复习巩固

A. 上新 一、关系模式 1. 决定属性 AB->C,函数依赖左侧出现为决定属性 AB->C,函数依赖右侧出现为非决定属性 候选键在决定属性中挑选&#xff0c;AB->C, CD->B中&#xff0c;A,D为侯选建 二、授权SQL 将权限授予用户&#xff08;grant <权限> on&#xf…

Git安装教程(图文安装)

Git Bash是git(版本管理器)中提供的一个命令行工具&#xff0c;外观类似于Windows系统内置的cmd命令行工具。 可以将Git Bash看作是一个终端模拟器&#xff0c;它提供了类似于Linux和Unix系统下Bash Shell环境的功能。通过Git Bash&#xff0c;用户可以在Windows系统中运行基于…

【个人笔记】如何用 Python 编写激活码解锁程序,方法二

目录 前言 第一步&#xff1a;编写激活码解锁程序&#xff08;激活码.py&#xff09; 第二步&#xff1a;修改需要解锁的程序&#xff08;1.py&#xff09; 总结 前言 在软件开发中&#xff0c;有时我们需要设计一种机制来保护程序&#xff0c;例如通过激活码来控制程序的…

【MySQL】:深入解析多表查询(上)

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; MySQL从入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. 多表关系1.1 一对多1.2 多对多1.3 一对一 二. 多表查询概述2.1 概述2.2 分类…

C51实现每秒向电脑发送数据(UART的含义)

其实核心的问题是&#xff1a;串口的通信方式 异步串行是指UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;&#xff0c;UART包含TTL电平的串口和RS232电平的串口 UART要实现异步通信的&#xff1a; UART是异步串行接口&#xff0c;通信双方使用时…

2024唐山国际门窗幕墙展览会

2024唐山国际门窗幕墙展览会 2024TangshanInternational Door and Window Curtain Wall Exhibition 2024年6月14-16日 地点&#xff1a;唐山南湖国际会展中心 唐山国际门窗幕墙博览会一 年一届&#xff0c;深耕京津冀核心区域&#xff0c;专注门窗行业高质量 发展&#x…

网络协议——HTTP协议

目录 ​编辑 一&#xff0c;HTTP协议基本认识 二&#xff0c;认识URL 三&#xff0c;http协议的格式 1&#xff0c;发送格式 2&#xff0c;回应格式 四&#xff0c;服务端代码 五&#xff0c;http报文细节 1&#xff0c;Post与Get方法 2&#xff0c;Content_lenth 3&…

基于Springboot的航班进出港管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的航班进出港管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结…