设计模式之单例模式(通俗易懂--代码辅助理解【Java版】)

文章目录

设计模式概述

创建型模式:工厂方法、抽象方法、建造者、原型、单例。

结构型模式有:适配器、桥接、组合、装饰器、外观、享元、代理。

行为型模式有:责任链、命令、解释器、迭代器、中介、备忘录、观察者、状态、策略、模板方法、访问者。

常用设计模式:

单例模式、工厂模式、代理模式、策略模式&模板模式、门面模式、责任链模式、装饰器模式、组合模式、builder模式。

1、单例模式概述

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

注意:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例

2、懒汉式:

java 复制代码
/**
 * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
 * 懒汉式:
 * 是否 Lazy 初始化:是
 * 是否多线程安全:否
 */
public class Signleton {
    private static Signleton signleton;
    private Signleton(){}
    //可以通过synchronized关键字保证线程安全
    public static Signleton getSignleton(){
        if(signleton == null){
            signleton = new Signleton();
        }
        return signleton;
    }
}

3、饿汉式

java 复制代码
/**
 * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
 * 饿汉式:
 * 是否 Lazy 初始化:否
 * 是否多线程安全:是
 */
class Signleton1{
    private static Signleton1 signleton1 = new Signleton1();

    private Signleton1(){}

    public static Signleton1 getSignleton1(){
        return signleton1;
    }
}

4、懒汉式:解决反射、序列化反序列化问题

java 复制代码
/**
 * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
 * 懒汉式:
 * 是否 Lazy 初始化:是
 * 是否多线程安全:否
 */
public class Signleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static Signleton signleton;

    private Signleton() {
        // 防止反射
        if (signleton != null) {
            throw new RuntimeException();
        }
    }

    // 可以通过synchronized关键字保证线程安全
    public static Signleton getSignleton() {
        if (signleton == null) {
            signleton = new Signleton();
        }
        return signleton;
    }


    /*
    序列化:当一个对象被序列化时,Java 将该对象的状态写入一个字节流。
    反序列化:当字节流被反序列化时,Java 将创建一个新的对象实例,并将字节流中的数据填充到这个新实例中。
    readResolve 方法:在对象被反序列化之后,Java 会调用这个方法。如果该方法存在,返回的对象将代替默认反序列化过程中创建的新对象。
     */
    private Object readResolve() {
        return signleton;
    }

}


 /**
     * 反射测试
     */
    @Test
    public void test(){
        //获取单例
        Signleton signleton = Signleton.getSignleton();
        Signleton signleton1 = Signleton.getSignleton();
        System.out.println(signleton.hashCode());
        System.out.println(signleton1.hashCode());

        //通过反射破坏单例
        try {
            Class<?> aClass = Class.forName("design.patterns.Signleton");
            Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            Signleton signleton2 = (Signleton) declaredConstructor.newInstance();
            System.out.println(signleton2.hashCode());
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
                 InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 序列化测试
     */
    @Test
    public void test1(){
        //获取单例
        Signleton signleton = Signleton.getSignleton();
        Signleton signleton1 = Signleton.getSignleton();
        System.out.println(signleton.hashCode());
        System.out.println(signleton1.hashCode());

        //序列化反序列化获取对象
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:/signleton.ser"));
            outputStream.writeObject(signleton1);
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("D:/signleton.ser"));
            Signleton signleton2 = (Signleton) inputStream.readObject();
            System.out.println(signleton2.hashCode());
        } catch (IOException | ClassNotFoundException e) {
            // throw new RuntimeException(e);
            e.printStackTrace();
        }
    }

5、懒汉式DCL(推荐)

懒汉式DCL(推荐):双重检查锁定(Double-Checked Locking)是用于减少同步开销,同时保证线程安全的一种优化方法。其核心思想是:在访问共享资源时,先进行一次非同步的检查,如果未初始化,再进入同步块进行第二次检查和初始化。这样可以避免每次调用获取实例方法时都需要进行同步,从而提升性能。

java 复制代码
/**
 * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
 * 懒汉式:
 * 是否 Lazy 初始化:是
 * 是否多线程安全:否
 */
public class Signleton implements Serializable {
  	private static final long serialVersionUID = 1L;
    private static volatile Signleton signleton;

    private Signleton() {}

    public static Signleton getSignleton() {
        if (signleton == null) {
        	synchronized(Signleton.class){
        		if(signleton == null){
        		 signleton = new Signleton();
        		}
        	}
           
        }
        return signleton;
    }
    
    /*
    序列化:当一个对象被序列化时,Java 将该对象的状态写入一个字节流。
    反序列化:当字节流被反序列化时,Java 将创建一个新的对象实例,并将字节流中的数据填充到这个新实例中。
    readResolve 方法:在对象被反序列化之后,Java 会调用这个方法。如果该方法存在,返回的对象将代替默认反序列化过程中创建的新对象。
     */
    private Object readResolve() {
        return signleton;
    }
}

6、应用场景

  • 资源共享:避免频繁的创建销毁某个对象,造成存好。比如:日志文件。
  • 控制资源:避免过多的对象产生,造成其他问题。比如网站的计数器。
  • 应用场景:
    • 日志管理器:避免频繁创建和销毁日志对象,确保日志文件只被一个实例操作,以便内容可以正确追加。
    • 网站计数器:全局唯一实例用于统计网站访问次数,避免并发更新问题。
    • Windows 回收站:整个系统运行过程中,回收站一直维护着唯一的一个实例。
    • 多线程的线程池:线程池需要方便控制池中的线程,单例模式确保线程池全局唯一。

7、单例线程池实现

java 复制代码
/**
 * 单例线程池 -- 应用场景
 */
public class ThreadPool1 {
    private static ThreadPool1 threadPool;
    // 定义接口
    private ExecutorService executorService;

    private ThreadPool1() {
        executorService = new ThreadPoolExecutor(
                5, // 核心线程数
                10, // 总线程数
                60, TimeUnit.MILLISECONDS, // 存活时间和单位
                new LinkedBlockingDeque<Runnable>(),  // 用于保存等待执行的任务的队列
                new ThreadFactory() {  // 用于创建新线程的工厂
                    // 定义原子操作的 int 类型。它可以在多线程环境下安全地进行自增、自减等操作而不需要同步
                    private AtomicInteger threadNumber = new AtomicInteger(1);
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r, "CustomThreadPool-thread-" + threadNumber.getAndIncrement());
                        // thread.setDaemon(true); // 设置为守护线程
                        thread.setPriority(Thread.NORM_PRIORITY);
                        return thread;
                    }
                },
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略,当任务队列满了且无法再接受任务时的处理策略
        );

    }

    public static synchronized ThreadPool1 getThreadPool() {
        if (threadPool == null) {
            threadPool = new ThreadPool1();
        }
        return threadPool;
    }

    public void submitTask(Runnable runnable) {
        executorService.submit(runnable);
    }

    public void shutdown() {
        executorService.shutdown();
    }
}



    /**
     * 单例线程池测试
     */
    @Test
    public void test2(){
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName() + " task is run");
        };

        // ThreadPool1.getThreadPool().submitTask(runnable);

        ThreadPool1 threadPool = ThreadPool1.getThreadPool();
        threadPool.submitTask(runnable);
        System.out.println(threadPool.hashCode());

        ThreadPool1 threadPool1 = ThreadPool1.getThreadPool();
        threadPool1.submitTask(runnable);
        System.out.println(threadPool1.hashCode());
    }

//输出:
158199555
158199555
CustomThreadPool-thread-2 task is run
CustomThreadPool-thread-1 task is run
  • SpringBoot中的大多数容器管理的Bean都是单例的,这些bean是应用程序级别的单例,也就是说不同用户共享同一个实例。比如@RestController、@Service、@Compoment、@Configuration注解修饰的类,默认都是单例。

8、总结

  • 优点:全局唯一性、节省资源、控制共享资源、延迟初始化、易于实现。
  • 缺点:隐藏依赖关系、难以测试、线程安全问题、难以扩展、生命周期问题、违背单一职责原则。
相关推荐
ZachOn1y3 分钟前
Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 垃圾收集器
java·jvm·后端·java-ee·团队开发·个人开发
攸攸太上16 分钟前
Java通配符的作用
java·学习·通配符
齐 飞29 分钟前
使用jackson将xml和对象、List相互转换
xml·java·spring boot·后端·list
liwulin050629 分钟前
java-在ANTLR中BaseListner的方法和词法规则的关系0.5.0
java·开发语言
SofterICer1 小时前
PE-PINCodes 规则
java
归去来兮★1 小时前
c++面向对象
java·开发语言·c++
Aimyon_361 小时前
DockerFile
java·开发语言
wrx繁星点点1 小时前
创建型模式-单例模式
java·开发语言·单例模式
guangzhi06331 小时前
JAVA执行引擎详细介绍
java·jvm
解孔明2 小时前
IDEA2023.1添加java虚拟机启动参数,打开断言
java·开发语言