1.1单例模式简介所谓类的单例设计模式,就是采取一定的方法来保证在整个软件系统中,对于某个类只能存在一个对象实例,而该类只提供了一个方法(静态方法)来获取它的对象实例。比如Hibernate的SessionFactory,它作为数据存储源的代理,负责创建Session对象。SessionFactory不是轻量级的。一般情况下,一个项目通常只需要一个SessionFactory,就会使用单例模式。单例模式的八种方式Hungrystyle(静态常量)Hungrystyle(静态代码块)Lazystyle(线程不安全)Lazystyle(线程安全,同步方法)Lazystyle(线程安全,同步代码块)Doublecheckstatic内部类枚举1.2Hungry-style(静态常量)应用实例步骤如下:构造函数私有化(防止new)内部对象创建类暴露静态公共方法getInstance代码实现publicclassSingletonDemo01{publicstaticvoidmain(String[]args){Singleton01instance01=Singleton01.getInstance();Singleton01instance02=Singleton01.getInstance();System.out.println(instance01==instance02);//trueSystem.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***饿汉式--静态常量*/classSingleton01{//1.构造方法私有化,外部不能newprivateSingleton01(){}//2.定义一个静态变量publicfinal静态Singleton01实例=newSingleton01();//3.对外暴露一个静态方法publicstaticSingleton01getInstance(){returninstance;}}优缺点解释优点:这种写法比较简单,就是类加载时实例化完成,避免了线程同步的问题。缺点:类加载时实例化完成,没有达到LazyLoading的效果。如果这个实例从头到尾都没有被使用过,就会造成内存的浪费。这种方式避免了基于类加载器机制的多线程同步问题。但是,实例是在加载类时实例化的。在单例模式下,大部分都是调用getInstance方法,但是类加载的原因有很多,所以不能保证有其他方式(或者其他静态方法)导致类加载。这时候初始化实例是达不到懒加载的效果的。结论:这种单例模式是可以的,可能会造成内存浪费。1.3饿了么风格(静态代码块)应用示例Singleton02instance02=Singleton02.getInstance();System.out.println(instance01==instance02);//trueSystem.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***饿汉式--静态代码块*/classSingleton02{//1.构造方法私有化,外部不能newprivateSingleton02(){}//2.定义静态变量publicstaticSingleton02实例;//3.静态代码块static{instance=newSingleton02();}//4.暴露一个静态方法publicstaticSingleton02getInstance(){returninstance;}}这种方法的优缺点其实和上面的方法类似,只是将类实例化的过程放在了静态代码块中,当加载类时,执行静态代码块中的代码进行初始化类的实例。优缺点同上。结论:这种单例模式是可以的,但是可能会造成内存浪费。1.4惰性(线程不安全)应用实例Singleton03instance02=Singleton03.getInstance();System.out.println(instance01==instance02);//trueSystem.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***惰性风格--threadunsafe*/classSingleton03{//1.构造方法私有化,外部不能newprivateSingleton03(){}//2.创建静态常量privatestaticSingleton03实例;//3.创建静态Method,供外部调用,当需要实例时,创建实例publicstaticSingleton03getInstance(){if(instance==null){instance=newSingleton03();}返回实例;}}LazyLoading效果各有优缺点,但只能在单线程下使用。如果在多线程下,一个线程进入了if(singleton==null)判断语句块,另一个线程还没来得及执行也通过了判断语句,此时会产生多个实例,导致线程失败.安全性,所以这种方法不能在多线程环境下使用。结论:在实际开发中不要使用这种方法。1.5惰性(线程安全,同步方法)应用程序实例Singleton04instance02=Singleton04.getInstance();System.out.println(instance01==instance02);//trueSystem.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***惰性风格--线程安全*/classSingleton04{//1.构造方法私有化,外部不能newprivateSingleton04(){}//2.创建静态常量privatestaticSingleton04实例;//3.为外部调用创建静态方法,只在需要实例的时候才创建实例//4.添加synchronized关键字,解决线程不完整问题publicstaticsynchronizedSingleton04getInstance(){if(instance==null){instance=newSingleton04();}返回实例;}}优缺点描述解决线程安全问题。效率太低了。当每个线程想要获取类的实例时,必须在执行getInstance()方法时进行同步。其实这个方法只执行了一次实例化代码,后面如果想获取这个类的实例,直接return即可。方法对于同步来说效率太低。结论:在实际开发中,不推荐使用这种方式。1.6Lazy-style(线程安全,同步代码块)应用实例Singleton05instance02=Singleton05.getInstance();系统输出。println(instance01==instance02);//trueSystem.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***惰性风格--线程安全--同步代码块*/classSingleton05{//1.构造方法私有化,外部不能newprivateSingleton05(){}//2.创建一个静态常量私有静态Singleton05实例;//3.创建静态方法供外部调用。当需要一个实例时,创建一个实例//4.使用同步代码块}}返回实例;}}优缺点解释结论:在实际开发中,不能使用这种方式。1.7仔细检查应用程序实例publicclassSingletonDemo06{publicstaticvoidmain(String[]args){Singleton06instance01=Singleton06.getInstance();Singleton06instance02=Singleton06.getInstance();System.out.println(instance01==instance02);//trueSystem.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***doublecheck*/classSingleton06{//1.构造方法私有化,外部不能直接newprivateSingleton06(){}//2.创建静态常量privatestaticSingleton06实例;//3.提供静态方法供外部调用//4.使用双重检查既达到了懒加载的效果,又保证了线程安全。推荐使用publicstaticSingleton06getInstance(){if(instance==null){synchronized(Singleton06.class){if(instance==null){instance=newSingleton06();}返回实例;}}返回实例;}}优点和缺点描述Double-Check概念经常用在多线程开发中。如代码所示,我们进行了两次if(singleton==null)检查,这样可以保证线程安全。这样实例化代码只用Executeit一次,后面再次访问时判断if(singleton==null)直接返回实例化对象,避免重复方法同步。线程安全,懒加载,效率高。结论:在实际开发中,推荐使用这种单例设计模式。1.8静态内部类应用实例publicclassSingletonDemo07{publicstaticvoidmain(String[]args){Singleton07instance01=Singleton07.getInstance();Singleton07instance02=Singleton07.getInstance();System.out.println(instance01==instance02);//trueSystem.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***使用static以内部类的方式实现单例模式*/classSingleton07{//1.构造函数是私有的,外部不能直接newprivateSingleton07(){}//2.创建一个静态内部类privatestaticclassSingleton07Instance{privatestaticfinalSingleton07INSTANCE=newSingleton07();}//3.创建一个静态方法供外部调用//4.JVM在加载类的时候不会加载静态内部类,只有在使用静态内部类的时候才加载,所以既保证了懒加载的效果,又保证了线程安全。publicstaticSingleton07getInstance(){returnSingleton07Instance.INSTANCE;}}优缺点说明该方法使用类加载机制保证实例初始化时只有一个线程。静态内部类方法在加载时不会立即实例化Singleton类,而是在需要实例化时调用getInstance方法加载SingletonInstance类,从而完成Singleton的实例化。类的静态属性只有在类第一次加载时才会被初始化,所以在这里,JVM帮我们保证了线程的安全。当类初始化时,其他线程无法进入。优点:避免线程不安全,利用静态内部类的特性实现懒加载,效率高。结论:推荐使用。1.9枚举应用实例publicclassSingletonDemo08{publicstaticvoidmain(String[]args){Singleton08instance01=Singleton08.INSTANCE;Singleton08instance02=Singleton08.INSTANCE;System.out.println(instance01==instance02);//真System.out.println("instance01hashcode="+instance01.hashCode());System.out.println("instance02hashcode="+instance02.hashCode());}}/***使用枚举实现单例模式*/enumSingleton08{INSTANCE;}优缺点说明这里使用JDK1.5中加入的枚举实现单例模式,不仅可以避免多线程同步问题,但也防止反序列化重新创建新对象。这种方法是EffectiveJava作者JoshBloch提倡的方法。结论:推荐使用。1.10单例模式在JDK应用的源码分析JDK中,java.lang.Runtime是经典的单例模式(饿了么风格)代码分析+Debug源码+代码描述publicclassRuntime{//使用静态常量实现单例Example模式私有静态运行时currentRuntime=newRuntime();/***返回与当前Java应用程序关联的运行时对象。*大多数Runtime
类的方法都是实例*方法,必须根据当前运行时对象调用。**@return与当前*Java应用程序关联的Runtime
对象。*/publicstaticRuntimegetRuntime(){returncurrentRuntime;}/**不要让任何人实例化这个类*/privateRuntime(){}}1.11单例模式的注意事项和细节单例模式保证系统内存中只存在该类的一个对象,节省系统资源.对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能。当你要实例化一个单例类时,一定要记得使用相应的获取对象的方法,而不是使用new。单例模式适用场景:需要频繁创建和销毁的对象,创建对象耗时或资源过多的对象(即:重量级对象),但经常使用的对象,工具对象,频繁访问数据库或文件的对象(如作为数据源、会话工厂等)。
