精华内容
下载资源
问答
  • 单例模式介绍: 单例模式指的是,保证一个类只有一个实例,并且提供一个全局可以访问的入口。举个例子,这就好比是“分身术”,但是每个“分身”其实都对应同一个“真身”。 使用单例模式的理由/好处: 其中一个理由...

    单例模式介绍:

    单例模式指的是,保证一个类只有一个实例,并且提供一个全局可以访问的入口。举个例子,这就好比是“分身术”,但是每个“分身”其实都对应同一个“真身”。

    使用单例模式的理由/好处:

    其中一个理由,那就是为了节省内存、节省计算。很多情况下,我们只需要一个实例就够了,如果出现了更多的实例,反而属于浪费。举个例子,我们就拿一个初始化比较耗时的类来说:

    public class ExpensiveResource {
    
        public ExpensiveResource() {
    
            field1 = // 查询数据库
    
            field2 = // 然后对查到的数据做大量计算
    
            field3 = // 加密、压缩等耗时操作
    
        }
    
    }
    

    上面的类在进行构造的时候,需要查询数据库并对查到的数据做大量计算,所以在第一次构造时,我们花了很多时间来初始化这个对象。但是假设我们数据库里的数据是不变的,并把这个对象保存在了内存中,那么以后就用同一个实例了,如果每次都重新生成新的实例,实在是没必要。

    第二个理由,那就是为了保证结果的正确。比如我们需要一个全局的计数器,用来统计人数,那么如果有多个实例,反而会造成混乱。

    另外呢,就是为了方便管理。很多工具类,我们只需要一个实例,那么我们通过统一的入口,比如通过 getInstance 方法去获取这个单例是很方便的,太多实例不但没有帮助,反而会让人眼花缭乱。

    单例模式有哪些适用场景:

    无状态的工具类:

    比如日志工具类,不管是在哪里使用,我们需要的只是它帮我们记录日志信息,除此之外,并不需要在它的实例对象上存储任何状态,这时候我们就只需要一个实例对象。

    全局信息类:

    比如我们在一个类上记录网站的访问次数,并且不希望有的访问被记录在对象 A 上,有的却被记录在对象 B 上,这时候我们就可以让这个类成为单例,需要计数的时候拿出来用即可。

    单例模式的几种实现方式:

    饿汉式

    饿汉式经典写法

    public class Singleton {
    	//定义一个私有的静态的 Singleton 变量,并进行初始化赋值(创建一个对象给变量赋值)
        private static Singleton singleton = new Singleton();
        //私有空参数构造方法,不让用户直接创建对象
        private Singleton(){}
        //定义一个公共的静态方法,返回 Singleton 对象
        public static Singleton getInstance(){
            return singleton;
        }
    }
    

    特点:
    由 JVM 的类加载机制保证了线程安全,因为在类被加载时便会把实例生成出来,避免了线程同步的问题,但同样也是其缺点,假设我们最终没有使用到这个实例的话,便会造成不必要的开销。

    下面我们再来看下饿汉式的变种——静态代码块形式

    饿汉式变种写法:静态代码块形式

    public class Singleton {
    
        private static Singleton singleton;
        
        static {
    
            singleton = new Singleton();
    
        }
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            return singleton;
        }
    
    }
    

    特点
    优缺点和经典写法一样,只是写法有些不同。

    懒汉式

    懒汉式经典写法

    public class Singleton {
    	//在类中定义一个私有的静态的 Singleton 变量,不进行初始化赋值
        private static Singleton singleton;
    	//私有空参数构造方法,不让用户直接创建对象
        private Singleton() {}
    	//在类中定义一个公共的静态成员方法,返回 Singleton 对象,保证无论调用多少次方法,只返回一个对象
        public static Singleton getInstance() {
    
            if (singleton == null) {
    
                singleton = new Singleton();
    
            }
    
            return singleton;
    
        }
    
    }
    
    

    特点:

    这种写法的优点在于,只有在 getInstance 方法被调用的时候,才会去进行实例化,所以不会造成资源浪费,但是在创建的过程中,并没有考虑到线程安全问题,如果有两个线程同时执行 getInstance 方法,就可能会创建多个实例。所以这里需要注意,不能使用这种方式,这是错误的写法

    为了避免发生线程安全问题,我们可以对前面的写法进行升级,那么线程安全的懒汉式的写法是怎样的呢。

    懒汉式线程安全写法:第一种

    
    public class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {}
    
        public static synchronized Singleton getInstance() {
    
            if (singleton == null) {
    
                singleton = new Singleton();
    
            }
    
            return singleton;
    
        }
    
    }
    

    特点:
    我们在 getInstance 方法上加了 synchronized 关键字,保证同一时刻最多只有一个线程能执行该方法,这样就解决了线程安全问题。但是这种写法的缺点也很明显:如果有多个线程同时获取实例,那他们不得不进行排队,多个线程不能同时访问,然而这在大多数情况下是没有必要的。

    懒汉式线程安全写法:第二种

    public class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
    
            if (singleton == null) {
    
                synchronized (Singleton.class) {
    
                    singleton = new Singleton();
    
                }
    
            }
    
            return singleton;
    
        }
    
    }
    

    特点:
    这种写法是错误的。将synchronized 放在getInstance方法内部,它的本意是想缩小同步的范围,但是从实际效果来看反而得不偿失。因为假设有多个线程同时通过了 if 判断,那么依然会产生多个实例,这就破坏了单例模式。

    所以,为了解决这个问题,在这基础上就有了“双重检查模式”。

    懒汉式线程安全写法:第三种

    public class Singleton {
    
        private static volatile Singleton singleton;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
    
            if (singleton == null) {
    
                synchronized (Singleton.class) {
    
                    if (singleton == null) {
    
                        singleton =  new Singleton();
    
                    }
    
                }
    
            }
    
            return singleton;
    
        }
    
    }
    

    特点:
    对singleton对象添加volatile,将
    这种写法的优点就是不仅做到了延迟加载,而且是线程安全的,同时也避免了过多的同步环节。推荐这种写法。我们重点来看一下 getInstance 方法,这里面有两层 if 判空,下面我们分别来看一下每个 if 的作用。

    这里涉及到一个常见的问题,面试官可能会问你,“为什么要 double-check?去掉第二次的 check 行不行?”这时你需要考虑这样一种情况,有两个线程同时调用 getInstance 方法,并且由于 singleton 是空的 ,所以两个线程都可以通过第一个 if。

    然后就遇到了 synchronized 锁的保护,假设线程 1 先抢到锁,并进入了第二个 if ,那么线程 1 就会创建新实例,然后退出 synchronized 代码块。接着才会轮到线程 2 进入 synchronized 代码块,并进入第二层 if,此时线程 2 会发现 singleton 已经不为 null,所以直接退出 synchronized 代码块,这样就保证了没有创建多个实例。假设没有第二层 if,那么线程 2 也可能会创建一个新实例,这样就破坏了单例,所以第二层 if 肯定是需要的。

    而对于第一个 check 而言,如果去掉它,那么所有线程都只能串行执行,效率低下,所以两个 check 都是需要保留。

    相信你可能看到了,我们在双重检查模式中,给 singleton 这个对象加了 volatile 关键字,那为什么要用 volatile 呢?这是因为 new 一个对象的过程,其实并不是原子的,至少包括以下这 3 个步骤:

    • 1、给 singleton 对象分配内存空间;
    • 2、调用 Singleton 的构造函数等,来进行初始化;
    • 3、把 singleton 对象指向在第一步中分配的内存空间,而在执行完这步之后,singleton 对象就不再是 null 了。

    这里需要留意一下这 3 个步骤的顺序,因为存在重排序,所以上面所说的三个步骤的顺序,并不是固定的。虽然看起来是 1-2-3 的顺序,但是在实际执行时,也可能发生 1-3-2 的情况,也就是说,先把 singleton 对象指向在第一步中分配的内存空间,再调用 Singleton 的构造函数。

    如果发生了 1-3-2 的情况,线程 1 首先执行新建实例的第一步,也就是分配单例对象的内存空间,然后线程 1 因为被重排序,所以去执行了新建实例的第三步,也就是把 singleton 指向之前的内存地址,在这之后对象不是 null,可是这时第 2 步并没有执行。假设这时线程 2 进入 getInstance 方法,由于这时 singleton 已经不是 null 了,所以会通过第一重检查并直接返回 singleton 对象并使用,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错。

    最后,线程 1“姗姗来迟”,才开始执行新建实例的第二步——初始化对象,可是这时的初始化已经晚了,因为前面已经报错了。

    到这里关于“为什么要用 volatile”问题就讲完了,使用 volatile 的意义,我认为主要在于呢,它可以防止刚讲到的重排序的发生,也就避免了拿到没完成初始化的对象。

    接下来要讲到的这种方式,静态内部类的写法,利用了类装载时由 JVM 所保证的单线程原则,进而保证了线程安全。

    懒汉式线程安全写法:第四种

    public class Singleton {
    
    
        private Singleton() {}
    
        private static class SingletonInstance {
    
            private static final Singleton singleton = new Singleton();
    
        }
     
        public static Singleton getInstance() {
    
            return SingletonInstance.singleton;
    
        }
    
    }
    

    相比于饿汉式在类加载时就完成实例化,这种静态内部类的写法并不会有这个问题,这种写法只有在调用 getInstance 方法时,才会进一步完成内部类的 singleton 的实例化,所以不存在内存浪费的问题。

    这里简单做个小总结,静态内部类写法与双重检查模式的优点一样,都是避免了线程不安全的问题,并且延迟加载,效率高。

    可以看出,静态内部类和双重检查的写法都是不错的写法,但是它们不能防止被反序列化生成多个实例,那有没有更好的写法呢?最后我们来看枚举方式的写法。

    枚举单例模式

    枚举单例模式的写法

    public enum Singleton {
    
        INSTANCE;
    
        public void myMethod() { 
    
        }
    
    }
    

    前面我们讲了饿汉式、懒汉式、双重检查、静态内部类、枚举这 5 种写法,有了这么多方法可以实现单例,这时你可能会问了,那我该怎么选择,用哪种单例的实现方案最好呢?

    Joshua Bloch(约书亚·布洛克)在《Effective Java》一书中明确表达过一个观点:“使用枚举实现单例的方法,虽然还没有被广泛采用,但是单元素的枚举类型已经成为了实现 Singleton 的最佳方法。”

    为什么他会更为推崇枚举模式的单例呢?这就不得不回到枚举写法的优点上来说了,枚举写法的优点有这么几个:

    首先就是写法简单枚举的写法不需要我们自己考虑懒加载、线程安全等问题。同时,代码也比较“短小精悍”,比任何其他的写法都更简洁,很优雅。

    第二个优点是线程安全有保障,枚举类的本质也是一个 Java 类,但是它的枚举值会在枚举类被加载时完成初始化,所以依然是由 JVM 帮我们保证了线程安全。

    前面几种实现单例的方式,其实是存在隐患的,那就是可能被反序列化生成新对象,产生多个实例,从而破坏了单例模式。接下来要说的枚举写法的第 3 个优点,它恰恰解决了这些问题。

    对 Java 官方文档中的相关规定翻译如下:“枚举常量的序列化方式不同于普通的可序列化或可外部化对象。枚举常量的序列化形式仅由其名称组成;该常量的字段值不存在于表单中。要序列化枚举常量,ObjectOutputStream 将写入枚举常量的 name 方法返回的值。要反序列化枚举常量,ObjectInputStream 从流中读取常量名称;然后,通过调用 java.lang.Enum.valueOf 方法获得反序列化常量,并将常量的枚举类型和收到的常量名称作为参数传递。”

    也就是说,对于枚举类而言,反序列化的时候,会根据名字来找到对应的枚举对象,而不是创建新的对象,所以这就防止了反序列化导致的单例破坏问题的出现。

    对于通过反射破坏单例而言,枚举类同样有防御措施。反射在通过 newInstance 创建对象时,会检查这个类是否是枚举类,如果是,就抛出 IllegalArgumentException(“Cannot reflectively create enum objects”) 异常,反射创建对象失败。

    可以看出,枚举这种方式,能够防止序列化和反射破坏单例,在这一点上,与其他的实现方式比,有很大的优势。安全问题不容小视,一旦生成了多个实例,单例模式就彻底没用了。

    总结:

    最后我来总结一下。今天我讲解了单例模式什么是,它的作用、用途,以及 5 种经典写法,其中包含了饿汉式、懒汉式、双重检查方式、静态内部类方式和枚举的方式,最后我们还经过对比,看到枚举方式在写法、线程安全,以及避免序列化、反射攻击上,都有优势。

    这里也跟大家强调一下,如果使用线程不安全的错误的写法,在并发情况下可能产生多个实例,那么不仅会影响性能,更可能造成数据错误等严重后果。

    如果是在面试中遇到这个问题,那么你可以从一开始的饿汉式、懒汉式说起,一步步分析每种写法的优缺点,并对写法进行演进,然后重点讲一下双重检查模式为什么需要两次检查,以及为什么需要 volatile 关键字,最后再说到枚举类写法的优点和背后的原理,相信这一定会为你的面试加分。

    另外在工作中,要是遇到了全局信息类、无状态工具类等场景的时候,推荐使用枚举的写法来实现单例模式。

    展开全文
  • java 最优单例模式

    2017-08-24 17:53:03
    java单例模式最优解决方案

              在这段代码中,因为SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次。

          由于SingletonClassInstance是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。


    public class SingletonClass { 
         
      private static class SingletonClassInstance { 
        private static final SingletonClass instance = new SingletonClass(); 
      } 
     
      public static SingletonClass getInstance() { 
        return SingletonClassInstance.instance; 
      } 
     
      private SingletonClass() { 
     
      } 
         
    }


    }
    展开全文
  • 最优单例模式Singletion

    2021-11-17 23:02:00
    public class Singletion { private Singletion() { }; private static volatile Singletion instance; public static Singletion getInstance() { if(instance==null) { synchronized(Singletion.class) ...
    public class Singletion {
    	private Singletion() {
    
    	};
    	private static volatile Singletion instance;
    	public static  Singletion getInstance() {
    		if(instance==null) {
    			synchronized(Singletion.class) {
    				if(instance==null) {
    					instance= new Singletion();	
    				}
    			}
    			
    		}
    		return instance;
    	}
    
    展开全文
  • c++: 单例模式(Singleton)的最优写法

    千次阅读 2020-12-08 20:01:16
    本例简介C++中单例模式最优写法。 实现 基础写法 下面的代码是C++单例的基础写法,在静态函数Singleton::getInstance()中定义了Singleton的静态变量对象,并返回此对象的引用。 由于C++函数的静态变量唯一性,可以...

    目的

    本例简介C++中单例模式的最优写法。

    实现

    基础写法

    下面的代码是C++单例的基础写法,在静态函数Singleton::getInstance()中定义了Singleton的静态变量对象,并返回此对象的引用。
    由于C++函数的静态变量唯一性,可以确保例子中s对象的唯一性,线程同步,以及静态对象间的依赖关系等问题。

    #include <iostream>
    
    class Singleton {
        public: static Singleton &getInstance() {
            static Singleton s;
            return s;
        }
    
        public: void test() {
            std::cout << "test" << std::endl;
        }
    };
    
    int main() {
        Singleton &s = Singleton::getInstance();
        s.test();
        return 0;
    }
    

    完整写法

    如果对代码要求比较严格,可以把该关闭的函数都关掉,这取决与你:):

    • 构造函数私有化,使得外部无法创建Singleton对象。
    • 关闭拷贝构造函数,右值拷贝构造函数。
    • 关闭赋值运算符重载函数。
    class Singleton {
        private: Singleton() { }
        Singleton(const Singleton &) = delete;
        Singleton(const Singleton &&) = delete;
        Singleton &operator=(const Singleton &) = delete;
    
        public: static Singleton &getInstance() {
            static Singleton s;
            return s;
        }
    
        public: void test() {
            std::cout << "test" << std::endl;
        }
    };
    

    对比

    New

    使用下面方法的也比较多,缺点是在无法保证getInstance()的线程安全性。如果工程比较大,会存在多个线程同时调用Singleton::getInstance()方法导致创建多实例的问题。

    class Singleton {
        private: static Singleton *instance;
    
        public: static Singleton *getInstance() {
            if (NULL == instance)
                instance = new Singleton();
    
            return instance;
        }
    };
    

    Lock

    所以可能会加一堆lock, 为了性能还写个double check, 比如下面这样,写起来比较麻烦,个人不太喜欢。

    class Singleton {
        private: static Singleton *instance;
    
        public: static Singleton *getInstance() {
            if (NULL == instance) {
                // TODO LOCK
                if (NULL == instance)
                    instance = new Singleton();
            }
    
            return instance;
        }
    };
    

    静态成员

    如果把函数内的静态变量变成类的静态成员变量呢?简单的工程行,复杂的不行。因为如果静态类之间有依赖,可能会导致C++的一些未定义的行为。

    • 下例中的Singleton::instance 保存在程序全局的静态数据区,instance初始化的时机是在程序的main()函数执行前。
    • 假设有SingletonB::instance,与Singleton::instance类似定义,也是静态类成员变量。SingletonB::instance和Singleton::instance的初始化顺序是未定义的,得看编译器的心情。
    • 如果Singleton::instance 的初始化在SingletonB::instance之前,而Singleton的构造函数中恰好需要引用到SIngleonB::instance,就很可能会出现一些未定义的行为。
    #include <iostream>
    
    class Singleton {
        private: static Singleton instance;
    
        public: static Singleton &getInstance() {
            return instance;
        }
        
        public: void test() {
            std::cout << "test" << std::endl;
        }
    };
    
    Singleton Singleton::instance;
    
    int main() {
        Singleton &s = Singleton::getInstance();
        s.test();
        return 0;
    }
    
    

    总结

    1. 如果项目小,建议上述单例中的简单写法就够了。
    2. 如果项目大,可以写全点儿。

    引用

    [1] Efficitive C++

    展开全文
  • 最优单例模式

    千次阅读 2014-12-15 14:25:31
    直奔主题,单例模式是一种表面简单实则很精妙的一个设计模式,网上有好多写法,什么饿汉懒汉什么多线程多处理器等等乱七八糟,个人觉得一种比较好的写法如下。 public class SingleTon { private SingleTon() { ...
  • 方法二:静态代码块:将类的实例化放在静态代码块中,与上述的静态常量一致,都是在类装载时创建单例,因此优缺点一致 方法三:静态内部类 : Singleton2在加载的时候不会被实例化,而是在需要实例化时(调用...
  • // 定义一个私有构造方法 private SingletonTest() { ...方法为单例模式的最佳实现。内存占用地,效率高,线程安全,多线程操作原子性。 转载于:https://www.cnblogs.com/daodaomanbuzhe/p/7065609.html
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 单例模式的最佳实现

    千次阅读 2018-04-30 17:06:42
    1.饿汉单例模式  在装载该单例类的时候就会创建类实例,实例代码如下所示: public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static ...
  • 单例模式是码农常用的设计模式,但未必用的是最优单例模式。 在此通过代码逐步分析出最优单例模式,分享给各位码农。   一说到单例模式,我想最快想到的就是饿汉的单例模式,即: Java代码  ...
  • 单例模式常用写法

    2019-01-03 12:49:52
     单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。一般介绍单例模式的书籍都会提到 饿汉式 和 懒汉式 这两种实现方式。但是除了这两种方式,本文还会介绍其他几种实现单例的方式,...
  • 1.懒汉模式 只适合单线程环境,在需要的时候才去创建对象实例(时间换空间)。 优点:在不要对象实例的时候,节省了内存空间。 缺点:在使用的时候会先判断是否为空,为空的话,才去创建对象。所以获取对象实例...
  • 单例模式介绍及它的使用场景单例模式是应用最广的模式,也是我最先知道的一种设计模式,在深入了解单例模式之前,每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式的实现。...
  • 备忘:最优单例模式

    千次阅读 2008-11-05 11:41:00
    public class QuestionManager { /** * 提供单例对象的静态内部类 */ private static class SingletonHolder { public static QuestionManager instance = new QuestionManage
  • https://blog.csdn.net/cselmu9/article/details/51366946
  • Android 单例模式比较和优化 单例模式 一、定义 单例模式保证了程序中只有一个实例但是可以在全局中访问到。 二、优势 1、由于只有一个实例,故可以减少内存开销 2、可以避免对资源的多重占用,避免对同一资源...
  • 单例最优写法

    千次阅读 2019-03-11 15:07:58
    单例模式有7中写法,本文给出最优写法 静态内部类 class Singleton { // 私有化构造方法,防止通过new的方式创建对象 private Singleton() { } // 静态内部类,懒加载 private static class LazyClass { ...
  • js代码-js单例模式最优

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,775
精华内容 1,910
关键字:

最优的单例模式