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

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

作者: 码神手记 | 来源:发表于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));
           }
    }
    

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

    相关文章

      网友评论

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

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