每天一个知识点深刻理解单例模式

  要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此signleton的构造方法是私有的。

  要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因signleton的构造方法是私有的。

  如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。

  如果单例对象一开始就被new singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。

  假设singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getinstance方法:

  因为instance是空,所以两个线程同时通过了条件判断,开始执行new操作:

  为了防止new singleton被执行多次,因此在new操作之加上synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。

  进入synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程a构建完对象,线程b也已经通过了最初的判空验证,不做第二次判空的话,线程b还是会再次构建instance对象。

  假设这样的场景,当两个线程一先一后访问getinstance方法的时候,当a线程正在构建对象,b线程刚刚进入方法:

  指令重排是什么意思呢?比如java中简单的一句 instance = new singleton,会被编译器编译成如下jvm指令:

  是这些指令顺序并非一成不变,有能会经过jvm和cpu的优化,指令重排成下面的顺序:

  当线,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程b抢占到cpu资源,执行 if(instance == null)的结果会是false,而返回一个没有初始化完成的instance对象。如下图所示:

  如何避免这一情况呢?我们需要在instance对象前面增加一个修饰符volatile。

  如此在线程b看来,instance对象的引用要么指向null,要么指向一个初始化完毕的instance,而不会出现某个中间态,保证了安全。

  classloader的加载机制来实现懒加载,并保证构建单例的线程安全。

  最后为了确认这两个对象是否真的是不同的对象,我们使用equals方法进行比较。毫无疑问,比较结果是false。

   volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主存中的最新值。有关volatile的详细原理,我在以后的漫画中会专门讲解。

  使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。

  对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readresolve方法。