单例模式引发的“血案” (2)

静态内部类方式
静态内部类这种方式是个人最不熟悉的,之前又一次面试中还被问过一个如何扩充类的问题,即Java中不支持多继承,如果想要复用多个类的属性如何做到?相对于将属性提取到接口中或通过自合模式复用,内部类的方式会更加优雅。对于单例同样可以借助内部类的特性优雅的处理,代码如下所示,注意看关于内部类加载的注释。

public class LazySingleton03 { private LazySingleton03(){} private static class LazyHolder{ private static final LazySingleton03 INSTANCE = new LazySingleton03(); } //借助了静态内部类的特性,其要被引用后才会装载到内存 //通常的理解是,只要是当前jar中的静态属性或方法都会被加载到内存,但静态内部类却不是,它只有在第一次调用getInstance方法,产生了LazyHolder的引用,才会被真正加载。 //实际上也是懒加载。 public static final LazySingleton03 getInstance(){ return LazyHolder.INSTANCE; } }

此外,枚举类型的单例借助其特性默认就是线程安全和防止反射破坏等行为,但并不是很适合大范围的使用。而登记器的方式实际上就是做一个Mapper其中存放所有的单例对象,需要时去获取即可,因此最推荐的仍然是内部类方式。

Tip
重排序
对于instance = new LazySingleton02(),其实际执行情况伪代码可能如下,可以看到Java内存模型分配内存并创建对象的方式和我们预想的不太一样,这部分会在Java并发编程系列文章中继续加强学习。

memory = allocate();//分配内存空间,C语言中应该很熟悉 instance = memory;//这时instance会变成非空,但还未初始化 ctor(instance);//初始化对象

synchronized关键字
通常基于不同的维度有如下几种用法,锁定范围越来越小,尽可能选择更小粒度的锁定范围可以获得更好的性能。
给类加锁:synchronized(XXXX.class)
给对象加锁:synchronized(this), public synchronized void test(){}
给代码块加锁:synchronized(lock){ ... }

Tip
单例模式的类和提供方法的静态类有什么区别?提供方法的静态类不是面向对象的思想的产物,相应的其没有封装、继承、多态等特性,简单来说你无法对提供方法的静态类进行扩展。

内部类

之前通过静态内部类方式实现单例引入了内部类这一重要概念,接下来将详细介绍内部类的相关概念和用法。如果在一个类的内部定义一个新的类型,就将这个新的类型称为内部类,其名称无需和文件名一致。特别的,内部类是一个编译时概念,一旦编译成功,就会成为完全不同的两个类,比如Outer.class和Outer$Inner.class。通常来说,内部类包括静态内部类、匿名内部类、成员内部类和局部内部类,重要性依次递减。

静态内部类与成员内部类
个人认为静态内部类最重要的一种内部类,比如常见的ReentrantLock中的Sync,NonfairSync、FairSync等一系列静态内部类。其通过static修饰,可以包含static数据和属性,且其无需创建外部类和内部类即可被使用。
成员内部类是最基本的一种内部类类型,其可以访问外部类的所有成员和方法,但不能含有static的变量和方法,因为成员内部类需要先创建外部类,之后才能创建自己,特别的,其可以通过外部类.this.属性的方式访问外部类同名属性,示例如下所示。

public class InnerClass { private static String staticName = "xionger"; private String objectName = "xiongda"; public static final class InnerClass01 {//静态内部类 private statißßc String name = InnerClassInterface.class.getName(); public static String getName() { return InnerClass.staticName + "--" + name; } } public class InnerClass02 {//成员内部类 public String getName() { return InnerClass.this.objectName;//注意这儿获取外部类属性的形式 } } }

匿名内部类
匿名内部类经常会被使用,比如使用线程、事件等场景,示例代码如下所示。

public class AnonymousInnerClass { public void createThread(){ Thread thd = new Thread(new Runnable() { @Override public void run() { System.out.println(AnonymousInnerClass.class.getName()); } }); } //注意方法参数上的final关键字,由于内部类编译时生成单独的.class文件,内部类与外部类不在同一文件。 //内部类是通过将传入的参数先通过构造器复制到自己内部再被使用的,因此为了保持数据的一致需要添加final。 public InnerClassInterface getInnerClass(final String prefixName){ return new InnerClassInterface(){ @Override public String getName() { return prefixName + "--suffixName"; } }; } } interface InnerClassInterface { String getName(); }

局部内部类的使用场景实在太少就不做介绍了。

类加载器

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjgwx.html