欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于单例模式的几种实现方式

程序员文章站 2022-07-12 19:27:14
...

单例模式的几种实现方式,直接上代码吧

package com.huang.sington;

 

/**

 * 饥汉模式

 * @author 黄

 *

 */

public class ExampleHunger {

 

 /**

  * final修饰的对象会在类加载的准备阶段进行赋值,造成内存的浪费

  */

 private final static ExampleHunger exampleFull = new ExampleHunger();

 

 private ExampleHunger() {

  

 }

 

 /**

  * 饱汉模式,会事先先类加载阶段将对象初始化到内存,因为对象不使用,会占用内存空间,造成资源浪费

  * @return

  */

 public static ExampleHunger getInstance() {

  return exampleFull;

 }

}

-----------------------------------------------------------------------------------------------------------------------------------------------

package com.huang.sington;

 

/**

 * 单例 懒汉模式

 * @author 黄

 *

 */

public class ExampleLazy {

 

 private static ExampleLazy exampleHunger = null;

 

 private ExampleLazy() {

  

 }

 

 

 /**

  * 加上synchronized关键字产生性能问题,不加会线程不安全

  * @return

  */

 public static synchronized ExampleLazy getInstance() {

  if(exampleHunger == null) {

   exampleHunger = new ExampleLazy();

  }

  return exampleHunger;

 }

}

----------------------------------------------------------------------------------------------------------------------------------------------

package com.huang.sington;

 

/**

 * 双重检查模式

 * @author 黄

 *

 */

public class ExampleDoubleCheck {

 

 /**

  * 需要加上volatile修饰,volatile保证内存可见性和禁止指令重排

  * 这里为什么要加这个关键字呢

  * exampleDoubleCheck = new ExampleDoubleCheck();

  * 这个动作分三步

  * 1、分配给实例内存空间

  * 2、调用构造方法初始化成员变量

  * 3、将exampleDoubleCheck指向分配的内存

  * 这里的三步1步骤是固定的,但是2、3步可能顺序不同,

  * 假如有一个线程初始化先执行第3步,在第二步还没有执行的情况下,另一个线程进来直接获取到exampleDoubleCheck对象

  * 这里获取到的对象在后续使用中可能会发生错误,所以要加上volatile关键字

  */

 private volatile static ExampleDoubleCheck exampleDoubleCheck = null;

 

 private ExampleDoubleCheck() {

  

 }

 

 public static ExampleDoubleCheck getInstance() {

  if(exampleDoubleCheck == null) {

   //加上全局锁

   synchronized (ExampleDoubleCheck.class) {

    if(exampleDoubleCheck == null) {

     exampleDoubleCheck = new ExampleDoubleCheck();

    }

   }

  }

  return exampleDoubleCheck;

 }

}

----------------------------------------------------------------------------------------------------------------------------------------------

package com.huang.sington;

 

/**

 * 采用静态内部类的方式获取

 * @author 黄

 *

 */

public class ExampleStaticInner {

 

 static {

  System.out.println("初始化类 ExampleStaticInner");

 }

 

 private ExampleStaticInner() {

  

 }

 

 /**

  * 这里使用用静态内部的方式实现单例,只有在调用getInstance方法时才加载静态内部类

  * 可以防止在饥汉模式中在类加载阶段就已经初始化的对象,造成内存的浪费

  * @return

  */

 public static ExampleStaticInner getInstance() {

  System.out.println("第一次加载ExampleStaticInner getInstance 方法");

  return ExampleSingtonHandler.exampleStaticInner;

 }

 

 public static void test() {

  System.out.println("第一次加载ExampleStaticInner test 方法");

 }

 

 private static class  ExampleSingtonHandler {

  private static final ExampleStaticInner exampleStaticInner = new ExampleStaticInner();

  static {

   System.out.println("初始化静态内部类 ExampleSingtonHandler");

  }

 }

}

关于静态内部类的实现方式,谢了一个测试方法看其加载过程

package com.huang.sington;

 

import org.junit.Test;

 

public class ExampleTest {

 

 /**

  * 测试静态内部类实行单例的加载过程

  */

 @Test

 public void staticInnerTest() {

  //停一秒

  try {

   Thread.sleep(1000);

  } catch (InterruptedException e) {

   e.printStackTrace();

  }

  

  System.out.println("调用test初始化ExampleStaticInner静态代码块和打印test文字开始");

  

  ExampleStaticInner.test();

  

  System.out.println("调用getInstance方法打印getInstance中文字并开始加载静态内部类");

  ExampleStaticInner.getInstance();

  

 }

}

运行结果

调用test初始化ExampleStaticInner静态代码块和打印test文字开始

初始化类 ExampleStaticInner

第一次加载ExampleStaticInner test 方法

调用getInstance方法打印getInstance中文字并开始加载静态内部类

第一次加载ExampleStaticInner getInstance 方法

初始化静态内部类 ExampleSingtonHandler