首页 > 编程语言 >Java设计模式-单例模式

Java设计模式-单例模式

时间:2024-07-23 22:50:55浏览次数:18  
标签:序列化 Java signleton Signleton 实例 单例 new 设计模式

Java常用设计模式-单例模式

Java Design Patterns:

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

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

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

常用设计模式:

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

单例模式

简介

确保一个类只有一个实例,并提供一个全局访问点

懒汉式:

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

饿汉式:

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

    private Signleton1(){}

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

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

/**
 * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
 * 懒汉式:
 * 是否 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();
        }
    }

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

这里也确保序列化安全。

/**
 * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
 * 懒汉式:
 * 是否 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;
    }
}

场景

资源共享:避免频繁的创建销毁某个对象,造成存好。比如:日志文件。

控制资源:避免过多的对象产生,造成其他问题。比如网站的计数器。

应用场景:

  • 日志管理器:避免频繁创建和销毁日志对象,确保日志文件只被一个实例操作,以便内容可以正确追加。

  • 网站计数器:全局唯一实例用于统计网站访问次数,避免并发更新问题。

  • Windows 回收站:整个系统运行过程中,回收站一直维护着唯一的一个实例。

  • 多线程的线程池:线程池需要方便控制池中的线程,单例模式确保线程池全局唯一。

    • /**
       * 单例线程池 -- 应用场景
       */
      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注解修饰的类,默认都是单例。

优点

控制资源的使用

  • 实例控制:确保一个类只有一个实例,避免了多个实例导致的资源浪费。例如,在数据库连接池或线程池的设计中,单例模式确保只创建一个连接池或线程池实例,从而控制资源的使用。
  • 节省资源:减少了系统的开销,避免了重复创建和销毁对象的高昂成本。

全局访问点

  • 统一管理:通过提供一个全局访问点,可以方便地管理和访问实例。比如,在日志记录系统中,通过单例模式可以确保所有日志记录都通过同一个实例进行处理,从而统一日志的输出格式和内容。
  • 一致性:所有对该实例的操作都通过统一的接口进行,确保了数据的一致性和完整性。

易于扩展

  • 延迟实例化:懒汉式单例模式在首次使用时才创建实例,避免了不必要的资源浪费。这种延迟加载的特性也便于在系统启动时减少初始化时间。

缺点

潜在的资源争用

  • 资源竞争:如果单例类内部使用了共享资源,而这些资源在高并发场景下没有妥善处理,可能导致资源竞争问题。例如,某个单例实例持有数据库连接对象,在高并发请求下可能导致连接池枯竭。

单点故障

  • 故障影响范围大:如果单例实例出现问题,整个系统的相关功能可能会受到影响。例如,日志记录系统的单例实例出现异常,可能导致整个系统无法正常记录日志。

难以测试

  • 测试复杂性:由于单例模式在整个应用程序中只有一个实例,单元测试时可能会导致测试的隔离性和独立性变差。例如,一个单例类的状态在多个测试方法之间共享,可能导致测试结果互相影响。

隐藏依赖关系

  • 依赖性不明确:单例模式通过全局访问点访问实例,可能会导致类与类之间的依赖关系变得不清晰。例如,一个类可能隐式依赖于某个单例类的状态,增加了系统的复杂性和维护成本。

标签:序列化,Java,signleton,Signleton,实例,单例,new,设计模式
From: https://www.cnblogs.com/zhiliu/p/18319807

相关文章

  • 深入理解Java虚拟机:JVM高级特性与最佳实践-第三章-垃圾收集器与内存分配策略
    在java内存运行时区域中的各个部分中,程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。但是Java堆和方法区这两个区域具有......
  • 面试题-Java 容器
    List和Set的区别是什么?Vector,ArrayList,LinkedList区别?ArrayList和LinkedList底层是怎么实现的?各自的特点是什么?HashSet和HashMap和的区别?TreeMap和TreeSet区别和实现原理HashMap,Hashtable的区别?HashMap,LinkedHashMap,TreeMap的区别?HashMap,和ConncurrentHashMap......
  • 41-50题矩阵和字符串 在Java中,将大写字符转换为小写字符的方法主要有以下几种:
    20240723一、数组最后几个和字符串的两个448.找到所有数组中消失的数字(和645.错误的集合差不多)283.移动零118.杨辉三角119.杨辉三角II661.图片平滑器(没看懂)598.区间加法II566.重塑矩阵303.区域和检索-数组不可变520.检测大写字母125.验证回文串二、在Jav......
  • JavaSE异常、今日面试题,解决git合并失败的终极篇!!!若以框架报错Invalid bound statement
    20240723一、JavaSE异常二、今日面试题三、解决git无法合并推送问题(终极篇!!!)1.直接把你写的复制出来,然后输入git命令1.1.解决办法一:保留本地的更改,中止合并->重新合并->重新拉取1.2解决办法二:舍弃本地代码,远端版本覆盖本地版本(慎重)2.然后复制回来,然后再合并推......
  • 《Java初阶数据结构》----3.<线性表---LinkedList与链表>
    目录前言一、链表的简介1.1链表的概念1.2链表的八种结构 重点掌握两种1.3单链表的常见方法三、单链表的模拟实现四、LinkedList的模拟实现(双链表)4.1 什么是LinkedList4.2LinkedList的使用五、ArrayList和LinkedList的区别 前言   大家好,我目前在学习......
  • 黑马pink JavaScript学习笔记_Web APIs Day2
    事件监听(绑定)什么是事件?事件是系统内发生的动作或者发生的事情。比如:用户点击页面上的一个按钮。什么是事件监听?就是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为注册事件比如:鼠标经过的时候,弹出一个alert“鼠标经过了~”语法元素对象.addEven......
  • 大规模Java应用程序的性能调优策略
    大规模Java应用程序的性能调优策略大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨大规模Java应用程序的性能调优策略。随着应用程序的规模增大,性能瓶颈可能会显现出来,因此对性能的优化是保证应用系统高效运行的关键。一、JVM参数调优......
  • Java并发编程的高级技术与最佳实践
    Java并发编程的高级技术与最佳实践大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们来深入探讨Java并发编程的高级技术与最佳实践。并发编程是Java开发中的一项重要技能,它能够充分利用多核处理器的优势,提高应用程序的性能和响应能力。本文将从高级......
  • 使用Docker和Kubernetes管理Java微服务
    使用Docker和Kubernetes管理Java微服务大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨如何使用Docker和Kubernetes来管理Java微服务。Docker和Kubernetes是现代微服务架构中不可或缺的工具,它们能够极大地简化应用程序的部署和管理,提高开发......
  • 分布式系统中的Java应用:挑战与解决方案
    分布式系统中的Java应用:挑战与解决方案大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨在分布式系统中使用Java的挑战与解决方案。分布式系统在处理大规模数据和高并发访问方面具有显著优势,但也面临诸多复杂性和挑战。本文将深入分析这些挑......