关于单例模式的几种实现方式
单例模式的几种实现方式,直接上代码吧
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
上一篇: Java之线程基础与并发同步
下一篇: centos7 搭建redis