找回密码
 立即注册
首页 业界区 业界 Java设计模式-单例模式

Java设计模式-单例模式

啸妹回 2025-6-5 19:47:27
Java常用设计模式-单例模式

Java Design Patterns:
创建型模式:工厂方法、抽象方法、建造者、原型、单例
结构型模式有:适配器、桥接、组合、装饰器、外观、享元、代理
行为型模式有:责任链、命令、解释器、迭代器、中介、备忘录、观察者、状态、策略、模板方法、访问者
常用设计模式:
单例模式、工厂模式、代理模式、策略模式&模板模式、门面模式、责任链模式、装饰器模式、组合模式、builder模式
单例模式

简介

确保一个类只有一个实例,并提供一个全局访问点
懒汉式:
  1. /**
  2. * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
  3. * 懒汉式:
  4. * 是否 Lazy 初始化:是
  5. * 是否多线程安全:否
  6. */
  7. public class Signleton {
  8.     private static Signleton signleton;
  9.     private Signleton(){}
  10.     //可以通过synchronized关键字保证线程安全
  11.     public static Signleton getSignleton(){
  12.         if(signleton == null){
  13.             signleton = new Signleton();
  14.         }
  15.         return signleton;
  16.     }
  17. }
复制代码
饿汉式:
  1. /**
  2. * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
  3. * 饿汉式:
  4. * 是否 Lazy 初始化:否
  5. * 是否多线程安全:是
  6. */
  7. class Signleton1{
  8.     private static Signleton1 signleton1 = new Signleton1();
  9.     private Signleton1(){}
  10.     public static Signleton1 getSignleton1(){
  11.         return signleton1;
  12.     }
  13. }
复制代码
懒汉式:解决反射、序列化反序列化问题
  1. /**
  2. * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
  3. * 懒汉式:
  4. * 是否 Lazy 初始化:是
  5. * 是否多线程安全:否
  6. */
  7. public class Signleton implements Serializable {
  8.     private static final long serialVersionUID = 1L;
  9.     private static Signleton signleton;
  10.     private Signleton() {
  11.         // 防止反射
  12.         if (signleton != null) {
  13.             throw new RuntimeException();
  14.         }
  15.     }
  16.     // 可以通过synchronized关键字保证线程安全
  17.     public static Signleton getSignleton() {
  18.         if (signleton == null) {
  19.             signleton = new Signleton();
  20.         }
  21.         return signleton;
  22.     }
  23.     /*
  24.     序列化:当一个对象被序列化时,Java 将该对象的状态写入一个字节流。
  25.     反序列化:当字节流被反序列化时,Java 将创建一个新的对象实例,并将字节流中的数据填充到这个新实例中。
  26.     readResolve 方法:在对象被反序列化之后,Java 会调用这个方法。如果该方法存在,返回的对象将代替默认反序列化过程中创建的新对象。
  27.      */
  28.     private Object readResolve() {
  29.         return signleton;
  30.     }
  31. }
  32. /**
  33.      * 反射测试
  34.      */
  35.     @Test
  36.     public void test(){
  37.         //获取单例
  38.         Signleton signleton = Signleton.getSignleton();
  39.         Signleton signleton1 = Signleton.getSignleton();
  40.         System.out.println(signleton.hashCode());
  41.         System.out.println(signleton1.hashCode());
  42.         //通过反射破坏单例
  43.         try {
  44.             Class<?> aClass = Class.forName("design.patterns.Signleton");
  45.             Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
  46.             declaredConstructor.setAccessible(true);
  47.             Signleton signleton2 = (Signleton) declaredConstructor.newInstance();
  48.             System.out.println(signleton2.hashCode());
  49.         } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
  50.                  InvocationTargetException e) {
  51.             e.printStackTrace();
  52.         }
  53.     }
  54.     /**
  55.      * 序列化测试
  56.      */
  57.     @Test
  58.     public void test1(){
  59.         //获取单例
  60.         Signleton signleton = Signleton.getSignleton();
  61.         Signleton signleton1 = Signleton.getSignleton();
  62.         System.out.println(signleton.hashCode());
  63.         System.out.println(signleton1.hashCode());
  64.         //序列化反序列化获取对象
  65.         try {
  66.             ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:/signleton.ser"));
  67.             outputStream.writeObject(signleton1);
  68.             ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("D:/signleton.ser"));
  69.             Signleton signleton2 = (Signleton) inputStream.readObject();
  70.             System.out.println(signleton2.hashCode());
  71.         } catch (IOException | ClassNotFoundException e) {
  72.             // throw new RuntimeException(e);
  73.             e.printStackTrace();
  74.         }
  75.     }
复制代码
懒汉式DCL(推荐):双重检查锁定(Double-Checked Locking)是用于减少同步开销,同时保证线程安全的一种优化方法。其核心思想是:在访问共享资源时,先进行一次非同步的检查,如果未初始化,再进入同步块进行第二次检查和初始化。这样可以避免每次调用获取实例方法时都需要进行同步,从而提升性能。
这里也确保序列化安全。
  1. /**
  2. * 单例设计模式:确保一个类只有一个对象实例,并提供一个全局访问点
  3. * 懒汉式:
  4. * 是否 Lazy 初始化:是
  5. * 是否多线程安全:否
  6. */
  7. public class Signleton implements Serializable {
  8.           private static final long serialVersionUID = 1L;
  9.     private static volatile Signleton signleton;
  10.     private Signleton() {}
  11.     public static Signleton getSignleton() {
  12.         if (signleton == null) {
  13.                 synchronized(Signleton.class){
  14.                         if(signleton == null){
  15.                          signleton = new Signleton();
  16.                         }
  17.                 }
  18.            
  19.         }
  20.         return signleton;
  21.     }
  22.    
  23.     /*
  24.     序列化:当一个对象被序列化时,Java 将该对象的状态写入一个字节流。
  25.     反序列化:当字节流被反序列化时,Java 将创建一个新的对象实例,并将字节流中的数据填充到这个新实例中。
  26.     readResolve 方法:在对象被反序列化之后,Java 会调用这个方法。如果该方法存在,返回的对象将代替默认反序列化过程中创建的新对象。
  27.      */
  28.     private Object readResolve() {
  29.         return signleton;
  30.     }
  31. }
复制代码
场景

资源共享:避免频繁的创建销毁某个对象,造成存好。比如:日志文件。
控制资源:避免过多的对象产生,造成其他问题。比如网站的计数器。
应用场景:

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

      1. /**
      2. * 单例线程池 -- 应用场景
      3. */
      4. public class ThreadPool1 {
      5.     private static ThreadPool1 threadPool;
      6.     // 定义接口
      7.     private ExecutorService executorService;
      8.     private ThreadPool1() {
      9.         executorService = new ThreadPoolExecutor(
      10.                 5, // 核心线程数
      11.                 10, // 总线程数
      12.                 60, TimeUnit.MILLISECONDS, // 存活时间和单位
      13.                 new LinkedBlockingDeque<Runnable>(),  // 用于保存等待执行的任务的队列
      14.                 new ThreadFactory() {  // 用于创建新线程的工厂
      15.                     // 定义原子操作的 int 类型。它可以在多线程环境下安全地进行自增、自减等操作而不需要同步
      16.                     private AtomicInteger threadNumber = new AtomicInteger(1);
      17.                     @Override
      18.                     public Thread newThread(Runnable r) {
      19.                         Thread thread = new Thread(r, "CustomThreadPool-thread-" + threadNumber.getAndIncrement());
      20.                         // thread.setDaemon(true); // 设置为守护线程
      21.                         thread.setPriority(Thread.NORM_PRIORITY);
      22.                         return thread;
      23.                     }
      24.                 },
      25.                 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略,当任务队列满了且无法再接受任务时的处理策略
      26.         );
      27.     }
      28.     public static synchronized ThreadPool1 getThreadPool() {
      29.         if (threadPool == null) {
      30.             threadPool = new ThreadPool1();
      31.         }
      32.         return threadPool;
      33.     }
      34.     public void submitTask(Runnable runnable) {
      35.         executorService.submit(runnable);
      36.     }
      37.     public void shutdown() {
      38.         executorService.shutdown();
      39.     }
      40. }
      41.     /**
      42.      * 单例线程池测试
      43.      */
      44.     @Test
      45.     public void test2(){
      46.         Runnable runnable = () -> {
      47.             System.out.println(Thread.currentThread().getName() + " task is run");
      48.         };
      49.         // ThreadPool1.getThreadPool().submitTask(runnable);
      50.         ThreadPool1 threadPool = ThreadPool1.getThreadPool();
      51.         threadPool.submitTask(runnable);
      52.         System.out.println(threadPool.hashCode());
      53.         ThreadPool1 threadPool1 = ThreadPool1.getThreadPool();
      54.         threadPool1.submitTask(runnable);
      55.         System.out.println(threadPool1.hashCode());
      56.     }
      57. //输出:
      58. 158199555
      59. 158199555
      60. CustomThreadPool-thread-2 task is run
      61. CustomThreadPool-thread-1 task is run
      复制代码

  • SpringBoot中的大多数容器管理的Bean都是单例的,这些bean是应用程序级别的单例,也就是说不同用户共享同一个实例。比如@RestController、@Service、@Compoment、@Configuration注解修饰的类,默认都是单例。
优点

控制资源的使用

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

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

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

潜在的资源争用

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

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

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

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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册