单例模式在实践中的应用 Singleton Pattern

1. 编写工具类

将构造方法声明为私有,并且可以抛出错误防止反射

/**
 * 日期工具类
 * @author chaowendao
 */
public class DateUtil {

	/**
	 * 私有构造方法并且防止反射
	 */
	private DateUtil() {
		throw new Error("防止反射");
	}
	
	/**
	 * 获得当前系统时间
	 * @return
	 */
	public static Timestamp getCurrentTime(){
		return new Timestamp(System.currentTimeMillis());
	}
	
	// 略

以下是未进行错误抛出时的反射攻击

	/**
	 * 反射再重新构造一个实例
	 * @throws Exception
	 */
	@Test
	public void test02() throws Exception {
		// 获得默认构造器
		Constructor<DateUtil> c = DateUtil.class.getDeclaredConstructor();
		// 跳过安全检查
		c.setAccessible(true);
		DateUtil util = (DateUtil) c.newInstance();
		DateUtil util2 = (DateUtil) c.newInstance();
		assertNotEquals(util, util2);
	}

2. 编写常量类

常量类可以用三种方式来表示,interface , 常规类,枚举

interface,不需要声明public static final 修饰符,默认表示为常量类

public interface Constant {
	String COMPLETE = "COMPLETE";
	String YES = "Y";
	int OFFSET = 0;
	String ORDER = "UPDATED_TIME";
}

常规类,使用final修饰符避免继承

public final class CodeConstant {
	public static final String  REGEX = "^[0_9]{8}$";
	public static final int CORE_POOL_SIZE = 10;
	public static final int MAXIMUN_POOL_SIZE = 30;
	public static final int KEEP_ALIVE_TIME = 60 * 1000;
	public static final int BLOCK_QUEUE_SIZE = 1000;
}

枚举,天生可以防止反射攻击(并且线程安全)的常量类,数量控制在64个以内

public enum DataSource {
	MASTER,
	SLAVER;
}

3. 使用静态内部类来实现单例模式(推荐)

public class Instance {
	/**
	 * 构造器私有化
	 */
	private Instance() {
	}
	/**
	 * 静态内部类
	 */
	private static class Inner  {
		/**
		 * 静态内部类中的静态变量
		 */
		private static final Instance INSTANCE = new Instance();
	}
	
	/**
	 * 方法调用时候才占用内存
	 * 多线程并发时,不存在排队问题
	 */
	public static Instance getInstance(){
		return Inner.INSTANCE;
	}
}

4.  饿汉式的方式实现单例模式(推荐)

public class HungrInstance {
	/**
	 * 初始化时即将单例加到内存中
	 */
	private static final HungryInstance INSTANCE = new LazyInstance();
	
	public static LazyInstance getInstance() {
		return INSTANCE;
	}
}	

5. 其他比较麻烦的方法

懒汉式加载,多线程时候可能创建多个实例(可以加上同步锁进行部分改善)

public class LazyInstance {
	
	private LazyInstance() {

	}
	private static LazyInstance instance = null;
	
	/**
	 * 在需要的时候进行实例创建,多线程时可能创建多个
	 */
	public static LazyInstance getInstance() {
		if (null == instance) {
			instance = new LazyInstance();
		}
		return instance;
	}
}

双重锁实现(DCK) ,锁造成的开销大

public class DCKInstance {
	/**
	 * 1.5以前的版本 volatile编译指令不起作用,可能导致高并发失效,1.5以后的OK
	 */
	private static volatile DCKInstance instance = null;
	private DCKInstance() {
	}
	public static DCKInstance getInstance() {
		if (null == instance) {
			synchronized (DCKInstance.class) {
				instance = new DCKInstance();
			}
		}
		return instance;
	}
}

容器单例模式

public class ContainerInstance {
	/**
	 * 线程安全的HashMap
	 */
	private static final Map<String, Object> MAP =  new ConcurrentHashMap<>();
	public static void putObject(String key, Object instance) {
		if (null != key && !"".equals(key) && null != instance) {
			// 保证一个实例
			if (! MAP.containsKey(key)) {
				MAP.put(key, instance);
			}
		}
	}
	/**
	 * 取出容器中存储的单例
	 */
	public static Object getInstance(String key) {
		return MAP.get(key);
	}
}

线程局部变量实现方式

public class ThreadInstance {
	/**
	 * 线程局部变量
	 */
	private static final ThreadLocal<ThreadInstance> LOCAL = new ThreadLocal<ThreadInstance>();
	private static ThreadInstance instance = null;
	public static ThreadInstance getInstance() {
		// 第一次线程调用时执行
		if (null == LOCAL.get()) {
			synchronized (ThreadInstance.class) {
				if (null == instance) {
					instance = new ThreadInstance();
				}
			}
			// 将存在或者新建的单例存入线程局部变量中
			LOCAL.set(instance);
		}
		return LOCAL.get();
	}
}	

单例模式实现列举:

实现方式优点缺点
饿汉模式简单方便(推荐)大量单例时消耗内存
懒汉模式需要时加载高并发下容易创建多个实例
懒汉模式(加锁)需要时加载高并发下可能创建多个实例,锁开销大
双重锁模式并发安全锁开销大,1.5版本以前可能失效
静态内部类模式方便(推荐)实现时多写一个类
枚举模式防止反射攻击并且线程安全,支持反序列化实例的类型是枚举,需要转换
容器模式可以存储多个单例占内存,可能存在覆盖问题
线程局部变量模式线程安全实现相对复杂

6. 单例模式一般不应该去实现Cloneable接口

该接口不需要构造函数也可以进行实例的复制,接口Cloneable然后重写其中的clone方法


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值