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

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

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

@

何谓单例模式?

专业术语

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

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

单例模式的优点

1、在内存中只有一个对象,节省内存空间。
2、避免频繁的创建销毁对象,可以提高性能。
3、避免对共享资源的多重占用。
4、可以全局访问。

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

首先我们要清楚单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。

单例模式的常规实现思路大致相同为以下三个步骤:

1、私有构造方法
2、指向自己实例的私有静态引用
3、以自己实例为返回值的静态的公有的方法

当然也可以理解为

1、私有化构造方法,让外部不能new。
2、本类内部创建对象实例【静态变量目的是为了类加载的时候创建实例】
3、提供一个公有的static静态方法(一般该方法使用getInstance这个名称),返回实例对象。

将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

单例模式的适用场景

由于单例模式有很多独特的优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

1、需要频繁实例化然后销毁的对象。
2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3、有状态的工具类对象。
4、频繁访问数据库或文件的对象。

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

单例模式的八种姿态写法

宜春强烈建议:如果是没有接触单例模式的读者朋友强烈建议你们动手敲一遍,不要复制,不然没效果!

还有一点就是,要真正轻而易举的理解单例模式,JVM的类加载知识是不能少的,不然你只是会敲的层次,啥?不懂类加载?放心,宜春就是要你会,要你理解透彻。

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

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

是不是心里暖暖的?宜春也不多哔哔了,直接撸码走起....

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

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

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

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

    //3、提供一个公有的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;
    }
}
/*
静态内部类单例方式
        1、这种方式采用了类加载机制来保证初始化实例时只有一个线程
        2、巧妙的将实例化Singleton操作放进getInstance方法中,getInstance方法返回静态内部类中实例化好的Singleton
        3、类的静态属性只会在第一次加载类的时候初始化,也就是只会初始化一次,在这里,JVM帮我们保证了线程的安全,类在初始化时,别的线程无法进入。
       
        优点:线程安全、利用静态内部类特点实现延迟加载、效率高
        开发中推荐使用这种静态内部类单例方式!

static静态内部特点:
1、外部类加载不会导致内部类加载,保证了其懒加载
*/

这个单例,宜春就不得不哔哔两句了,要清楚这个单例,必须要明白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中添加的枚举来实现单例模式优点:
		 1、不仅能避免多线程同步问题 
		 2、还能防止反序列化重新创建新的对象

枚举方式单例是由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 默认是单例模式的,解析。

单例模式总结

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

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

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

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

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

在这里插入图片描述

posted @ 2019-11-26 09:12  宜春  阅读(755)  评论(0编辑  收藏  举报