美文网首页程序员
你真能说清楚单例模式吗?

你真能说清楚单例模式吗?

作者: 码神手记 | 来源:发表于2020-05-31 18:06 被阅读0次

你真能说清楚单例模式吗?

码神手记——资深攻城狮的私房笔记。微信公众平台/知乎/头条/简书同步发文。感谢关注与转发。

单例模式.PNG

核心作用

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

常见应用场景

  • Windows的Task Manager(任务管理器)是很典型的单例模式
  • Windows的Recycle Bin(回收站)是单例应用。在系统运行中,回收站只维护仅有的一个实例。
  • 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据都new一个对象去读取。
  • 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  • 应用程序的日志应用,一般都使用单例模式实现,一般因为共享的日志文件一直处于打开状态,只能有一个实例去操作,否则内容不好追加。
  • 数据库连接池的设计一般也采用单例模式。
  • 操作系统的文件系统,也是单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  • 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理。
  • 在Servlet编程中,每个Servlet也是单例。
  • 在Spring MVC框架/struts1框架中,控制器对象也是单例。

单例模式的优点

  • 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其它依赖对象,则可以在应用启动时直接产生一个单例对象,然后永久驻留内存。
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

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

  1. 饿汉式(线程安全,调用效率高。但是,不能延时加载)
  2. 懒汉式(线程安全,调用效率不高。但是,可以延时加载)
  3. 双重检测锁式(由于指令重排,在多线程环境下可能出现引用不空,但对象未初始化。不建议使用。)
  4. 静态内部类式(线程安全、调用效率高,可以延时加载)
  5. 枚举单例(线程安全,调用效率高,不能延时加载)

如何选用?

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

代码示例

饿汉式

package com.liu.singleton;

/**
 * 测试饿汉式单例模式
 *
 * @author Steve
 *
 */
public class SingletonDemo01 {
        //类初始化时,立即加载(没有延时加载的优势)。加载类时是线程安全的。
        private static SingletonDemo01 instance = new SingletonDemo01();

        private SingletonDemo01() {
               //避免使用反射调用构造器时创建新的对象
               if (instance !=null) {
                      throw new RuntimeException("请使用getInstance方法获取对象" );
               }
       }
        //方法没有同步,调用效率高
        public static SingletonDemo01 getInstance() {
               return instance ;
       }
        // 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
        private Object readResolve() throws ObjectStreamException {
               return instance ;
       }
}

懒汉式

package com.liu.singleton;

/**
 * 测试懒汉式单例模式
 *
 * @author Steve
 *
 */
public class SingletonDemo02 {
        private static SingletonDemo02 instance ;

        private SingletonDemo02() {
          //避免使用反射调用构造器时创建新的对象
              if (instance !=null) {
                      throw new RuntimeException("请使用getInstance方法获取对象" );
              }
       }

        public static synchronized SingletonDemo02 getInstance() {
               if (null == instance) {
                      instance = new SingletonDemo02 ();
              }
               return instance ;
       }
        // 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
        private Object readResolve() throws ObjectStreamException {
               return instance ;
       }
}

双重检查锁式

package com.liu.singleton;

/**
 * 双重检查锁实现单例模式
 *
 * @author Steve
 *
 */
public class SingletonDemo03 {
        private static SingletonDemo03 instance = null;

        private SingletonDemo03() {
          //避免使用反射调用构造器时创建新的对象
               if (instance !=null) {
                      throw new RuntimeException("请使用getInstance方法获取对象" );
              }
       }

        public static SingletonDemo03 getInstance() {
               if (null == instance) {
                     SingletonDemo03 sc;
                      synchronized (SingletonDemo03.class) {
                            sc = instance;
                             if (sc == null ) {
                                   synchronized (SingletonDemo03.class) {
                                          if (sc == null ) {
                                                sc = new SingletonDemo03();
                                         }
                                  }
                                   instance = sc;
                            }
                     }
              }
               return instance ;
       }
        // 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
        private Object readResolve() throws ObjectStreamException {
               return instance ;
       }
}

静态内部类式

package com.liu.singleton;

/**
 * 静态内部类实现单例模式
 *
 * 外部类没有static属性,则不会像饿汉式那样立即加载对象
 *
 * 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的,由JVM保证。
 *
 * instance是static final类型,保证了在内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
 *
 * 兼备了并发高效调用和延迟加载的优势
 *
 * @author Steve
 *
 */
public class SingletonDemo04 {
        private static class SingletonClassInstance {
               private static final SingletonDemo04 instance = new SingletonDemo04();
       }

        public static SingletonDemo04 getInstance() {
               return SingletonClassInstance.instance;
       }

        private SingletonDemo04() {
          //避免使用反射调用构造器时创建新的对象
               if (instance !=null) {
                      throw new RuntimeException("请使用getInstance方法获取对象" );
              }
       }
        // 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
        private Object readResolve() throws ObjectStreamException {
               return SingletonClassInstance.instance ;
       }
}

枚举式

package com.liu.singleton;

/**
 * 使用枚举实现单例模式
 *
 * 优点:实现简单、枚举本身是单例模式。由JVM从根本上提供保障,避免通过反射和反序列化创建对象的漏洞
 *
 * 缺点:无延迟加载
 *
 * @author Steve
 *
 */
public enum SingletonDemo05 {

        /**
        * 这个枚举元素,本身就是单例的
        */
        INSTANCE;
        /**
        * 添加自己需要的操作
        */
        public void singletonOperation() {

       }
}

多线程测试各种实现方式的效率

package com.liu.singleton;

import java.util.concurrent.CountDownLatch;

/**
 * 使用CountDownLatch测试多线程环境下各个单例模式实现方式的效率
 *
 * @author  Steve
 *
 */
public class Client3 {
        public static void main(String[] args) throws InterruptedException {
               long start = System.currentTimeMillis();
               int threadNum = 10;
               final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

               for (int i = 0; i < 10; i++) {
                      new Thread(new Runnable() {
                             @Override
                             public void run() {
                                   for (int i = 0; i < 1000000; i++) {
                                         Object o = SingletonDemo01.getInstance();
                                  }
                                  countDownLatch.countDown();
                            }

                     }).start();
              }
              countDownLatch.await(); // main线程阻塞,知道计数器值变为0,才会继续往下执行

               long end = System.currentTimeMillis();
              System. out.println("总耗时:" + (end - start));
       }
}

码神手记——资深攻城狮的私房笔记。微信公众平台/知乎/头条/简书同步发文。感谢关注与转发。

相关文章

  • 你真能说清楚单例模式吗?

    你真能说清楚单例模式吗? 码神手记——资深攻城狮的私房笔记。微信公众平台/知乎/头条/简书同步发文。感谢关注与转发...

  • 你真的会写单例吗?

    你真的会写单例吗? 摘录来源 单例的正确姿势 Java单例模式可能是最简单也是最常用的设计模式,一个完美的单例需要...

  • 单例模式(Java内部类加载顺序)

    你真的会写单例模式吗——Java实现Android设计模式源码解析之单例模式深度分析 Java 的枚举类型:枚举的...

  • 设计模式之单例模式详解

    设计模式之单例模式详解 单例模式写法大全,也许有你不知道的写法 导航 引言 什么是单例? 单例模式作用 单例模式的...

  • 设计模式

    手写单例模式(线程安全) 你知道几种设计模式?单例模式是什么?Spring中怎么实现单例模式?

  • 【设计模式】单例模式

    单例模式 常用单例模式: 懒汉单例模式: 静态内部类单例模式: Android Application 中使用单例模式:

  • Android设计模式总结

    单例模式:饿汉单例模式://饿汉单例模式 懒汉单例模式: Double CheckLock(DCL)实现单例 Bu...

  • 单例模式的优化方案

    参考:你真的会写单例吗? 单例模式最常见的就是懒汉式加载: 例: 当调用getInstance方法时才去创建对象,...

  • Java单例模式

    转载: 你真的会写单例模式吗-------Java实现 单例模式可能是代码最少的模式了,但是少不一定意味着简单,想...

  • 设计模式之你真的了解单例模式么?

    问题思考 你知道什么是单例模式么?你能写出一个性能有保障并且安全的单例模式么? 首先我们先明确单例模式的概念,单例...

网友评论

    本文标题:你真能说清楚单例模式吗?

    本文链接:https://www.haomeiwen.com/subject/itsrzhtx.html