现在的位置: 首页 > 架构设计 > 正文

什么是单例模式?单例模式的八种姿态写法

2020年02月25日 架构设计 ⁄ 共 7447字 ⁄ 字号 评论关闭

  何谓单例模式?

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

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

  单例模式的优点:

  1、在内存中只有一个对象,节省内存空间。

  2、避免频繁的创建销毁对象,可以提高性能。

  3、避免对共享资源的多重占用。

  4、可以全局访问。

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

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

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

  1、私有构造方法;

  2、指向自己实例的私有静态引用;

  3、以自己实例为返回值的静态的公有的方法。

  当然也可以理解为:

  1、私有化构造方法,让外部不能new。

  2、本类内部创建对象实例【静态变量目的是为了类加载的时候创建实例】。

  3、提供一个公有的static静态方法(一般该方法使用getInstance这个名称),返回实例对象。

  将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;

  在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

  单例模式的适用场景

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

  1、需要频繁实例化然后销毁的对象。

  2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象。

  3、有状态的工具类对象。

  4、频繁访问数据库或文件的对象。

  在Spring MVC框架中的controller 默认是单例模式的!

  单例模式的八种姿态写法

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

  还有一点就是,要真正轻而易举的理解单例模式,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(线程安全)方式在开发中不推荐使用,主要是效率太低了*/

  姿态五:懒汉式3 同步代码块(线程安全)

  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静态内部特点,也就是外部类加载不会导致内部类加载!

  姿态八:枚举

  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

  * Runtime that allows the application to interface with

  * the environment in which the application is running. The current

  * runtime can be obtained from the getRuntime method.

  *

  * 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 Runtime are instance

  * methods and must be invoked with respect to the current runtime object.

  *

  * @return the Runtime 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提倡的,开发中推荐使用!

抱歉!评论已关闭.