【设计模式】单例模式的八种姿态写法分析

前言
网上泛滥流传单例模式的写法种类,有说7种的,也有说6种的,固然也不排除说5种的,他们说的有错吗?其实没有对与错,刨根问底,写法终究是写法,其本质精髓大致一致!所以彻底不必去追究写法的多少,有这个时间还不如跟着宜春去网吧偷耳机、去田里抓青蛙得了,一每天的....java

言归正传...单例模式是最经常使用到的设计模式之一,熟悉设计模式的朋友对单例模式绝对不会陌生。同时单例模式也是比较简单易理解的一种设计模式。面试

@spring

何谓单例模式?

专业术语数据库

单例模式是一种经常使用的软件设计模式,其定义是单例对象的类只能容许一个实例存在。许多时候整个系统只须要拥有一个的全局对象,这样有利于咱们协调系统总体的行为。好比在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,而后服务进程中的其余对象再经过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。编程

单例模式,简单的说就是 一个类只能有一个实例,而且在整个项目中都能访问到这个实例。设计模式

单例模式的优势

一、在内存中只有一个对象,节省内存空间。
二、避免频繁的建立销毁对象,能够提升性能。
三、避免对共享资源的多重占用。
四、能够全局访问。安全

单例模式实现总体思路流程

首先咱们要清楚单例模式要求类可以有返回对象一个引用(永远是同一个)和一个得到该实例的方法(必须是静态方法,一般使用getInstance这个名称)。服务器

单例模式的常规实现思路大体相同为如下三个步骤:多线程

一、私有构造方法
二、指向本身实例的私有静态引用
三、以本身实例为返回值的静态的公有的方法

固然也能够理解为
一、私有化构造方法,让外部不能new。
二、本类内部建立对象实例【静态变量目的是为了类加载的时候建立实例】
三、提供一个公有的static静态方法(通常该方法使用getInstance这个名称),返回实例对象。

将该类的构造方法定义为私有方法,这样其余处的代码就没法经过调用该类的构造方法来实例化该类的对象,只有经过该类提供的静态方法来获得该类的惟一实例;
在该类内提供一个静态方法,当咱们调用这个方法时,若是类持有的引用不为空就返回这个引用,若是类保持的引用为空就建立该类的实例并将实例的引用赋予该类保持的引用。

单例模式的适用场景

因为单例模式有不少独特的优势,因此是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

一、须要频繁实例化而后销毁的对象。
二、建立对象时耗时过多或者耗资源过多,但又常常用到的对象。
三、有状态的工具类对象。
四、频繁访问数据库或文件的对象。

在后面我将会讲到JDK中的Runtime类就是使用的饿汉式单例!在Spring MVC框架中的controller 默认是单例模式的!

单例模式的八种姿态写法

宜春强烈建议:若是是没有接触单例模式的读者朋友强烈建议大家动手敲一遍,不要复制,否则没效果!

还有一点就是,要真正垂手可得的理解单例模式,JVM的类加载知识是不能少的,否则你只是会敲的层次,啥?不懂类加载?放心,宜春就是要你会,要你理解透彻。

别翻了,这篇文章绝对让你深入理解java类的加载以及ClassLoader源码分析【JVM篇二】

其实上面的这篇文章特别重要,上面这篇文章的重要性懂的天然懂,不懂的但愿能理解宜春的一片好意,去看一下吧,实在看不懂看不下去在回来看这篇文章就行了,再大不了就把博主一块儿按在马桶盖盖上....

是否是内心暖暖的?宜春也很少哔哔了,直接撸码走起....

姿态一:饿汉式1(静态变量)

package singletonPattern;
//饿汉式(静态变量)

class Singleton{
    //一、私有化构造方法,让外部不能new
    private Singleton(){

    }
    //二、本类内部建立对象实例【静态变量目的是为了类加载的时候建立实例】
    private final static Singleton instance=new Singleton();

    //三、提供一个公有的static静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}
//如下是测试代码=====================

public class SingletenDemo1 {
    public static void main(String[] args) {
        Singleton singleton=Singleton.getInstance();
        Singleton singleton2=Singleton.getInstance();
//验证一:
        System.out.println(singleton==singleton2);
//验证二:
        System.out.println(singleton.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

//运行结果:
//        true
//        460141958
//        460141958

/*
饿汉式(静态变量)方法

优势:写法简单,在类加载的时候就完成了实例化,同时也就避免了线程同步问题,所以线程安全
缺点:因为是在类加载时就完成了实例化,没有达到懒加载的效果。若是一直没有使用过这个实例,就形成了内存的浪费!

总结:这种方式基于ClassLoader类加载机制避免了多线程的同步问题,只不过instance属性在类加载就实例化,在单例模式中大多数都是调用getInstance方法,
     因为getInstance方法是static静态的,调用它确定会触发类加载!可是触发类加载的缘由有不少,咱们不能保证这个类会经过其余的方式触发类加载(好比调用了其余的static方法)
     这个时候初始化instance就没有达到lazy loading 懒加载的效果,可能形成内存的浪费!

     饿汉式(静态变量)这种方式可使用可是会形成内存的浪费!

     */

姿态二:饿汉式2(static静态代码块)

package singletonPattern;
//饿汉式2(static静态代码块)

class Singleton2{
    private Singleton2(){

    }

    private static Singleton2 instance;

    static{ //把建立单例对象的操做放进了static静态代码块中==============
        instance = new Singleton2();
    }

    public static Singleton2 getInstance(){
        return instance;
    }
}
//饿汉式2(static静态代码块)其实和第一种饿汉式(静态变量)方法差很少,其优缺点一致!
//惟一不一样的就是把建立单例对象的操做放进了static静态代码块中

姿态三:懒汉式1(线程不安全)

package singletonPattern;
//懒汉式1(线程不安全)
class Singleton3{
    private Singleton3(){

    }

    private static Singleton3 instance;

    public static Singleton3 getInstance(){
        if(instance == null){
            instance=new Singleton3();
        }
        return instance;
    }
}
/*
懒汉式(线程不安全)的这种方式起到了懒加载的效果,但只能在单线程下使用。
若是在多线程下,一个线程进入了if(singleton==null)判断语句块,还没执行产生实例的句子,另外一个线程
又进来了,这时会产生多个实例,因此不安全。

结语:懒汉式(线程不安全)在实际开发中,不要使用这种方式!!存在潜在危险
*/

姿态四:懒汉式2(线程安全)

package singletonPattern;
//懒汉式2(线程安全)
class Singleton4{
    private Singleton4(){

    }

    private static Singleton4 instance;

    public static synchronized Singleton4 getInstance(){
        if(instance == null){
            instance=new Singleton4();
        }
        return instance;
    }
}

/*
懒汉式2(线程安全)方式

优势:线程安全
缺点:效率过低,每次调用getInstance方法都要进行同步

结语:懒汉式2(线程安全)方式在开发中不推荐使用,主要是效率过低了*/

姿态五:饿汉式2(static静态代码块)

package singletonPattern;
//懒汉式3 同步代码块(线程安全) 可是不知足单例,在多线程下依旧会有多个实例
class Singleton5{
    private Singleton5(){

    }

    private static Singleton5 instance;

    public static  Singleton5 getInstance(){
        if(instance == null){   //多线程状况下可能多个线程进入这个if块
            synchronized (Singleton5.class){  //到这里只会一个一个建立实例,虽然安全,可是就再也不是单例了
                instance=new Singleton5();
            }
        }
        return instance;
    }
}
/*
懒汉式3 同步代码块(线程安全) 可是不知足单例,依旧会有多个实例

结语:懒汉式3 同步代码块(线程安全)方式在开发中不使用 ,实际上这个单例设计的有点搞笑*/

姿态六:双重检查单例

package singletonPattern;
//双重检查应用实例方式
class Singleton6{
    private Singleton6(){}

    private static volatile Singleton6 singleton;

    public static Singleton6 getInstance(){
        if(singleton==null){
            synchronized(Singleton6.class){
                if(singleton == null){
                    singleton= new Singleton6();
                }
            }
        }
        return singleton;
    }
}
/*
双重检查应用实例方式:

线程安全、延迟加载、效率较高

结语:开发中推荐使用!
*/

这个时候博主就得哔哔几句了,细心的童鞋会发现有一个Volatile关键字,完了,没见过,小白童鞋慌了!

Volatile 变量具备 synchronized 的可见性特性,可是不具有原子特性。这就是说线程可以自动发现 volatile 变量的最新值。

这种实现方式既能够实现线程安全地建立实例,而又不会对性能形成太大的影响。它只是第一次建立实例的时候同步,之后就不须要同步了,从而加快了运行速度。

姿态七:静态内部类单例

package singletonPattern;
//static静态内部类单例

class Singleton7{
    private Singleton7(){}

    private static volatile Singleton7 instance;

    //写一个static静态内部类,给该类添加一个static静态instance属性
    private static class SingletonInstance{
        private static final Singleton7 SINGLETON_7=new Singleton7();
    }

    //
    public static synchronized Singleton7 getInstence(){
        return SingletonInstance.SINGLETON_7;
    }
}
/*
静态内部类单例方式
        一、这种方式采用了类加载机制来保证初始化实例时只有一个线程
        二、巧妙的将实例化Singleton操做放进getInstance方法中,getInstance方法返回静态内部类中实例化好的Singleton
        三、类的静态属性只会在第一次加载类的时候初始化,也就是只会初始化一次,在这里,JVM帮咱们保证了线程的安全,类在初始化时,别的线程没法进入。
       
        优势:线程安全、利用静态内部类特色实现延迟加载、效率高
        开发中推荐使用这种静态内部类单例方式!

static静态内部特色:
一、外部类加载不会致使内部类加载,保证了其懒加载
*/

这个单例,宜春就不得不哔哔两句了,要清楚这个单例,必需要明白static静态内部特色,也就是外部类加载不会致使内部类加载!

姿态八:饿汉式2(static静态代码块)

package singletonPattern;
//使用枚举

import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader;

enum Singleton8{
    INSTANCE;
    public void methodName(){
        System.out.println("测试数据");
    }
}
/*

枚举方式的枚举:
推荐写法,简单高效。充分利用枚举类的特性,只定义了一个实例,且枚举类是自然支持多线程的。
借助JDK1.5中添加的枚举来实现单例模式优势:
         一、不只能避免多线程同步问题 
         二、还能防止反序列化从新建立新的对象

枚举方式单例是由Effective java做者Josh Bloch提倡的,结语:推荐使用!
*/

固然也能够测试一下

public class SingletonDemo8 {
    public static void main(String[] args) {
        Singleton8 instance = Singleton8.INSTANCE;
        Singleton8 instance2 = Singleton8.INSTANCE;
        System.out.println(instance==instance2);

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());

        instance.methodName();
    }
}

运行结果:

true
460141958
460141958
测试数据

属实没毛病!

JDK源码中单例模式的应用

先来看一段Runtime 的源码吧,并分析一下其使用的是种单例模式!

/**
 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.
 *
 * @author  unascribed
 * @see     java.lang.Runtime#getRuntime()
 * @since   JDK1.0
 */
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

这应该不难看出吧!若是看不出的话只能说明你真的尚未理解单例模式,我其实想说单例模式实际上是23种设计模式中最简单的一个,只是写法比较多而已!同时面试官通常都会问单例模式,它已是很基础的了,问的稍微有点水平就是问你单例模式在JDK中哪里运用到了,显然JDK中的Runtime其实它使用的就是饿汉式单例!正如注释所说,每个java应用程序都有一个Runtime实例。Runtime的单例模式是采用饿汉模式建立的,意思是当你加载这个类文件时,这个实例就已经存在了。

Runtime类能够取得JVM系统信息,或者使用gc()方法释放掉垃圾空间,还可使用此类运行本机的程序。

==还有就是spring Mvc 中的controller 默认是单例模式的,解析。==

单例模式总结

一、饿汉式(静态变量)这种方式可使用,可是没有达到 lazy loading 懒加载的效果会形成内存的浪费!开发中不建议使用。
二、饿汉式(static静态代码块)其实和第一种饿汉式(静态变量)方法差很少,其优缺点一致!惟一不一样的就是把建立单例对象的操做放进了static静态代码块中
三、懒汉式(线程不安全)起到了懒加载的效果,但只能在单线程下使用。在实际开发中,不要使用这种方式!!!
四、懒汉式2(线程安全)方式线程安全可是效率过低,每次调用getInstance方法都要进行同步。因此在开发中不推荐使用。 五、懒汉式3
同步代码块(线程安全)方式在开发中不使用 ,实际上这个设计有点搞笑哈哈。
六、双重检查应用实例方式,线程安全、延迟加载、效率较高。所以开发中推荐使用!
七、静态内部类单例方式线程安全、利用静态内部类特色实现延迟加载、效率高。 开发中推荐使用这种静态内部类单例方式!
八、借助JDK1.5中添加的枚举来实现单例模式不只能避免多线程同步问题还能防止反序列化从新建立新的对象。枚举方式单例是由Effective java做者Josh Bloch提倡的,开发中推荐使用!

单例模式必须考虑到在多线程的应用场合下的使用,毕竟如今的服务器基本上都是多核的了。

若是本文对你有一点点帮助,那么请点个赞呗,谢谢~

最后,如有不足或者不正之处,欢迎指正批评,感激涕零!若是有疑问欢迎留言,绝对第一时间回复!

欢迎各位关注个人公众号,一块儿探讨技术,向往技术,追求技术,说好了来了就是盆友喔...

在这里插入图片描述

相关文章
相关标签/搜索