设计模式-单例
上周开发的时候需要做一个校验用户姓名格式的类,因为提供的是公共功能,类似于工具类,所以最好拿来就能用。内部逻辑大体如下所示:
public class NameValidateUtil { // 校验姓名格式是否满足要求 public boolean validateName (String name) { // 判断是不是汉字的姓名 if (isChineseName(name)) { // 是汉字,就用汉字的校验逻辑 return validateChineseName(name); } else { // 否则是英文,用英文的校验逻辑 return validateEnglishName(name); } } private boolean validateEnglishName(String name) { // 此处省略校验逻辑 return true; } private boolean validateChineseName(String name) { // 此处省略校验逻辑 return true; } private boolean isChineseName(String name) { // 此处省略判断逻辑 return false; } }
可以看到,校验逻辑比较多,不适合放在一个方法里,如果给每一个方法都加上static也可以做到拿来即用,但这样的设计不够优雅。既然不适合直接用静态的方式,那么很容易就会想到可以用单例模式来实现。其实说实话,在这之前虽然博主也将23中设计模式了解过一遍,但是大多未在项目中实际操作过,所以对设计模式使用的必然性缺少认识,而这一次便让我切身体会到了适合单例模式的使用场景。如上所述的,如果一个类提供的是公共的功能,且不方便给其方法设置成static静态的,那么就可以使用单例模式来实现。
对于单例模式,园友们应该都知道老生常谈的那两种:饿汉式和懒汉式。基本的实现方式大家都清楚,此处就不贴了。饿汉式虽然不会出现线程安全的情况,但除非某些特殊场景,一般都不使用。而一般的懒汉式,对于线程安全的解决办法是双重检查锁(dubbo-check),如下所示:
public class NameValidateUtil { // 私有化构造器,让外部无法对该类实例化 private NameValidateUtil(){} private static NameValidateUtil nameValidateUtil; public static NameValidateUtil getSingleton(){ if (nameValidateUtil == null) { synchronized (NameValidateUtil.class) { if (nameValidateUtil == null) { nameValidateUtil = new NameValidateUtil(); } } } return nameValidateUtil; }
}
但由于JVM的内存模型中允许指令重排序,即在
nameValidateUtil = new NameValidateUtil();
这句的三步(a.分配内存地址;b.初始化;c.将内存地址传给引用)中,b与c可能会c先执行,b后执行,这样在高并发的情况下,就可能会出现某个线程获取到了未完成初始化的对象的情况。为避免此情况,可以通过给nameValidateUtil加volatile关键字的方式来避免重排序。
或者使用静态内部类的方式(静态内部类是在使用的时候才会初始化,且JVM能保证只初始化一次),代码如下所示:
public class NameValidateUtil { // 私有化构造器,让外部无法对该类实例化 private NameValidateUtil(){} private static NameValidateUtil nameValidateUtil; public static NameValidateUtil getSingleton(){ return InnerNameValidateUtil.innerValidate; } private static class InnerNameValidateUtil { static NameValidateUtil innerValidate = new NameValidateUtil(); } }
九月份加班成魔,学习进度严重落后,十月份应该就回归正轨了,务必抓紧时间学习!