搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > CSDN > 趣说单例模式——选班长

趣说单例模式——选班长

CSDN 2019-03-15
举报

趣说单例模式——选班长

作者 | 倪升武

责编 | 屠敏

注:本文人物形象均为原创,人物姓名均为虚构。

“码农大学”是“互联省”的一所名牌大学,学习气氛浓厚,不管是学校的环境还是学生综合素质,都非常高。开学的第一天,同学们都兴致勃勃,这不,一起来看下设计模式的课堂里。

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

自我介绍完之后,老师开始进入本节课的主题了。

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

提出这个问题后,大家开始相互讨论起来。

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长


趣说单例模式——选班长

懒汉式单例


于是小夏开始实现这个班长类:首先,我们要在班长类中将构造方法私有化,这样是防止在其他地方被实例化,就出现多个班长对象了。然后我们在班长类中自己 new 一个班长对象出来。最后给外界提供一个方法,返回这个班长对象即可。如下(代码可以左右滑动):

public class Monitor {
   private static Monitor monitor = null;
   private Monitor() {}
   public static Monitor getMonitor() {
       if (monitor == null) {
           monitor = new Monitor();
       }
       return monitor;
   }
}

趣说单例模式——选班长

趣说单例模式——选班长

小美开始了他的分析:我觉得小夏的代码还是不能保证一个班长实例的,因为存在线程安全问题。假如线程A执行到了monitor = new Monitor();,此时班长对象还没创建,线程B执行到判断 monitor == null时,条件为true,于是也进入到if里面去执行monitor = new Monitor();了,这样内存中就出现了两个班长实例了。

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

于是,小美根据自己的思路,将小夏的代码做了修改,在获取班长对象的方法上面加了个 synchronized 关键字,这样就能解决线程安全问题了。

public static synchronized Monitor getMonitor() {
   if (monitor == null) {
       monitor = new Monitor();
   }
   return monitor;
}

趣说单例模式——选班长

小夏觉得这种修改不太好,于是和小美讨论起来:小美,你这样改虽然可以解决线程安全问题,但是效率太差了,不管班长对象有没有被创建好,后面每个线程并发走到这,可想而知,都做了无用的等待呀。

趣说单例模式——选班长

还没等小美说话,小刘举起手来,他想到了更好的解决方案:老师,我有更好的办法!我们不能在方法上添加 synchronized关键字,但可以在方法内部添加。比如:

public static Monitor getMonitor() {
   if (monitor == null) {
       synchronized (Monitor.class) {
           if (monitor == null) {
               monitor = new Monitor();
           }
       }
   }
   return monitor;
}

趣说单例模式——选班长

趣说单例模式——选班长

小刘开始给小夏解释到:这判断是有目的的,第一层判断如果 monitor 实例不为空,那皆大欢喜,说明对象已经被创建过了,直接返回该对象即可,不会走到 synchronized 部分,所以班长对象被创建了之后,不会影响到性能。

第二层判断是在 synchronized 代码块里面,为什么要再做一次判断呢?假如 monitor 对象是 null,那么第一层判断后,肯定有很多线程已经进来第一层了,那么即使在第二层某个线程执行完了之后,释放了锁,其他线程还会进入 synchronized 代码块,如果不判断,那么又会被创建一次,这就导致了多个班长对象的创建。所以第二层起到了一个防范作用。

趣说单例模式——选班长

趣说单例模式——选班长

在同学们踊跃发言和讨论之后,老师做了一下简短的总结:同学们都分析的很棒,这就是“懒汉式”单例模式,为什么称为“懒汉式”呢?顾名思义,就是一开始不创建,等到需要的时候再去创建对象。

小刘的这个“懒汉式”单例模式已经写的很不错了,不过这里还有一个问题,虽然可能已经超出了本课程的要求了,但是我还是来补充一下,在定义班长对象时,要加一个 volatile 关键字。即:

private static volatile Monitor monitor = null;

趣说单例模式——选班长

趣说单例模式——选班长

于是,老师开始和同学们分析:我们先看下 monitor = new Monitor();,在这个操作中,JVM主要干了三件事:

1、在堆空间里分配一部分空间;

2、执行 Monitor 的构造方法进行初始化;

3、把 monitor 对象指向在堆空间里分配好的空间。

把第3步执行完,这个 monitor 对象就已经不为空了。

但是,当我们编译的时候,编译器在生成汇编代码的时候会对流程顺序进行优化。优化的结果不是我们可以控制的,有可能是按照1、2、3的顺序执行,也有可能按照1、3、2的顺序执行。

如果是按照1、3、2的顺序执行,恰巧在执行到3的时候(还没执行2),突然跑来了一个线程,进来 getMonitor() 方法之后判断 monitor 不为空就返回了 monitor 实例。此时 monitor 实例虽不为空,但它还没执行构造方法进行初始化(即没有执行2),所以该线程如果对那些需要初始化的参数进行操作那就悲剧了。但是加了 volatile 关键字的话,就不会出现这个问题。这是由 volatitle 本身的特性决定的。

关于 volatile 的更多知识已经超出了本课程的范围了,感兴趣的同学可以课后自己研究研究。


趣说单例模式——选班长

饿汉式单例


看到大家一直在激烈的讨论问题,小帅一直在座位上思考……终于他也发言了。

趣说单例模式——选班长

小帅一边说一边写起了代码:

public class Monitor {
   private static Monitor monitor = new Monitor ();
   private  Monitor () {}
   public static Monitor getMonitor() {
       return monitor;
   }
}

小帅继续说到,在定义的时候就将班长对象创建出来,这样还没有线程安全问题。

趣说单例模式——选班长

老师正要讲“饿汉式”单利模式,刚好小帅说出来了,于是就借题发挥:小帅的这种方式就叫做“饿汉式”单例模式,顾名思义,一开始就创建出来,比较“饥饿”,这种方式是不存在线程安全问题的。这个“饿汉式”单利相对来说比较简单,也很好理解,我就不多说了。


趣说单例模式——选班长

单例模式的扩展


听了小帅的发言,小夏开始纳闷了,他开始和旁边的小刘讨论起来,老师好像看出来了小夏有疑惑,于是……

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

老师借着这个问题,继续讲课:我们要知道,万物存在即合理,但是也不是十全十美的,不管是“懒汉式”还是“饿汉式”,都有它们各自的优缺点以及使用场景。

针对刚刚小夏提到的问题,“饿汉式”虽然简单粗暴,而且线程安全,但是它不是延迟加载的,也就是说类创建的时候,就必须要把这个班长实例创建好,而不是在需要的时候才创建,这是第一点。

我再举个例子,也许更能说明问题:假如在获取班长对象的时候,需要传一个参数进去呢?也就是说,我在选班长的时候有个要求,比如我想选一个身高高于175cm的人做班长,那么我在获取班长实例对象时,需要传一个身高参数,该方法就应该这样设计:

public static Monitor getMonitor(Long height) {……}

针对这种情况,“饿汉式”就不行了,就得用“懒汉式”单例了。

静态内部类

老师看了看手表,离下课还有16分钟,于是还想再讲点东西。

趣说单例模式——选班长

于是老师又提出了个问题给同学们:班长这个对象有个属性是不会变的,那就是他所在的班级,所以班级可以直接定义好,老师翻到了PPT的下一页,如:

public class Monitor {
   public static String CLASS_INFO = "通信工程(1)班";
   private static Monitor monitor = new Monitor ();
   private Monitor () {}
   public static Monitor getMonitor() {
       return monitor;
   }
}

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

老师解释到:是可以获取,但是这样获取的话,因为都是static修饰的,调用Monitor.CLASS_INFO时,也会执行构造方法将monitor对象初始化,但是我现在不想初始化班长对象(因为会影响性能),我只想要获取他的班级信息。

趣说单例模式——选班长

趣说单例模式——选班长

于是老师把继续把 PPT 翻到了下一页:

public class Monitor {
   public static String CLASS_INFO = "通信工程(1)班";
   /**
    * 静态内部类,用来创建班长对象
    */

   private static class MonitorCreator {
       private static Monitor monitor = new Monitor();
   }
   private Monitor() {}
   public static Monitor getInstance() {
       return MonitorCreator.monitor;
   }
}

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

小美好像发现了新大陆,非常兴奋:我还发现了一个特点,使用静态内部类这种方式,也是实现懒加载的,也就是说当我们调用 getInstance 方法的时候,才会去初始化班长对象,这和“懒汉式”是一样的效果;而且在内部类中,初始化这个班长对象的时候,是直接 new 出来的,这个和“饿汉式”很像。哇,难道这就是两种方式的结合体吗?

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

枚举单例

老师意犹未尽,但看了看表,还有4分钟就下课了,感觉讲不完了,于是最后给同学们抛出一种方式,让同学们下课后自己研究研究。

趣说单例模式——选班长


趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

于是老师把PPT又往后翻了一页:

public enum Monitor {
   INSTANCE;
   // 其他任意方法
}

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

老师见同学们激情澎湃,于是决定把这个讲完:上面这段枚举代码比较抽象,我说具体点,我们就举前面提到的例子,比如班长有个属性是所属班级,那么我现在要创建这样一个班长实例,我可以这么写:

public enum Monitor {
   INSTANCE("通信工程(1)班");
   private String classInfo;
   EnumSingleton(String classInfo) {
       this.classInfo = classInfo;
   }
   // 省略get set方法
}

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

于是老师继续往下讲:当你们工作之后,实际场景肯定不像课堂上说的这么简单,就像小刘说的那样,如果有很多属性呢?而且属性可以改变该怎么做呢?这时候,我们可以借助枚举类来实现单例,为什么说“借助”呢?我先创建一个班长对象,里面是属性(这里我就用一个属性代表一下,你们可以认为有很多属性),如下:

public class Monitor {
   private String classInfo;
   // 省略get set 方法
}

接下来,我就要“借助”枚举,创造出班长这个单例实体,而且支持属性可修改,大家请看PPT:

public enum EnumSingleton {
   INSTANCE;
   private Monitor monitor;
   EnumSingleton() {
       monitor = new Monitor();
   }
   public Monitor getMonitor() {
       return monitor;
   }
}

老师对着PPT讲到:Monitor 类就是我们的班长类,我放到私有构造方法中初始化了,然后枚举类中同样提供一个 getMonitor 方法给外界提供这个班长对象,模式和前面讲的单例差不多。我们可以通过 EnumSingleton.INSTANCE.getMonitor(); 即可获取到 monitor 对象。

趣说单例模式——选班长


趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

趣说单例模式——选班长

就这样,老师被几个学生架到生活区的小饭馆了,当然咯,最后还少不了买单……

作者简介:倪升武,CSDN 博客专家,CSDN达人课作者。硕士毕业于同济大学,曾先后就职于 eBay、爱奇艺、华为。目前在科大讯飞从事Java领域的软件开发,他的世界不仅只有coding。

声明:本文为作者投稿,版权归其个人所有。

 热 文 推 荐 

print_r('点个好看吧!');
var_dump('点个好看吧!');
NSLog(@"点个好看吧!");
System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧!\n");
cout << "点个好看吧!" << endl;
Console.WriteLine("点个好看吧!");
fmt.Println("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"

点击“阅读原文”,打开 CSDN App 阅读更贴心!

喜欢就点击“好看”吧!

版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《趣说单例模式——选班长》的版权归原作者「CSDN」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

举报