版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shentanweilan9/article/details/80734512
#私有构造函数强化singleton属性
公有的静态成员是一个final域,成员的声明很清楚的表达了这个类是一个singleton。
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { } public void leaveTheBuilding() { System.out.println("Who a baby, I'm outta here!"); } // This code would normally appear outside the class! public static void main(String[] args) { Elvis elvis = Elvis.INSTANCE; elvis.leaveTheBuilding(); } }
提供一个公有的静态方法,而不是公有的静态final域。该方式提供了更大的灵活性,在不改变API的前提下,可以把该类改成singleton或者非singleton的。
public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis() { } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding() { System.out.println("Who a baby, I'm outta here!"); } // This code would normally appear outside the class! public static void main(String[] args) { Elvis elvis = Elvis.getInstance(); elvis.leaveTheBuilding(); } }
一般来说,第一种方法效率稍微高一些,然后,采用第一种方法实现singleton后,就没有改变的余地了,当你想把该类改成非singleton,显然是不行的了。所以,除非确实确定该类是一个singleton,那就用第一个方法吧。用第2种方法的时候,假如该类实现了serializable接口,那应该重写(override)readResolve()方法,否则再反序列化的时候是会产生一个新的实例,这与singleton相违背了!
现代jvm几乎都能够将静态工厂方法进行调用内联化。
- 方法调用 函数调用先转移到该函数的内存地址,程序内容读取完毕后转到函数执行前方法。这种操作要求保护现场并记忆执行此地址,执行完恢复现场。这就是通常说的出栈和入栈,这需要一定时间和内存的开销。
- 内联函数。怎么解决这个性能消耗问题呢,这个时候需要引入内联函数了程序编译时,编译器将程序中的调用表达式用目标函数体直接替换。这样就不会产生转去转回的问题,但是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间代销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。
- 写C代码时,我们都学到将一些简短的逻辑定义在宏里。这样做的好处是,在编译器编译的时候会将用到该宏的地方直接用宏的代码替换。这样就不再需要象调用方法那样的压栈、出栈,传参了。性能上提升了。内联函数的处理方式与宏类似,但与宏又有所不同,内联函数拥有函数的本身特性(类型、作用域等等)。在C++里有个内联函数,使用inline关键字修饰。另外,写在Class定义内的函数也会被编译器视为内联函数
- C++是否为内联函数由自己决定,Java由编译器决定。Java不支持直接声明为内联函数的,如果想让他内联,你只能够向编译器提出请求: 关键字final修饰 用来指明那个函数是希望被JVM内联的。
public final void dealSomthing() {}
jvm内联运行时优化,a、短方法更利于jvm优化。(流程更明显,作用域更短,副作用也更明显),长方法直接就跪了。b、小方法频繁执行,jvm会执行内联。(它会把方法的调用替换成方法体本身)
private int add4(int x1, int x2, int x3, int x4) { return add2(x1, x2) + add2(x3, x4); } private int add2(int x1, int x2) { return x1 + x2; } // 运行一段时间后 jvm会把代码翻译成下面这样 private int add4(int x1, int x2, int x3, int x4) { return x1 + x2 + x3 + x4; }
从Java1.5之后 实现Singleton还有第三种方法,只需要编写一个包含单个元素的枚举类型。他和公有域方法类似。但是它无偿提供了序列化机制(自由序列化),绝对的防止多次实例化(线程安全) 即使面对复杂的序列化和反射攻击的时候。
enum SingletonDemo{ INSTANCE; public void otherMethods(){ System.out.println("Something"); } }
我们之前用的枚举 一般都是多个属性的常量 用于switch
enum Color{ RED,GREEN,BLUE; } public class Hello { public static void main(String[] args){ Color color=Color.RED; int counter=10; switch (color){ case RED: System.out.println("Red"); color=Color.BLUE; break; case BLUE: System.out.println("Blue"); color=Color.GREEN; break; case GREEN: System.out.println("Green"); color=Color.RED; break; } } } }
- enum是通过继承了Enum类实现的,enum结构不能够作为子类继承其他类,但是可以用来实现接口。此外,enum类也不能够被继承,在反编译中,我们会发现该类是final的
- enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。这里展开说下这个private构造器,如果我们不去手写构造器,则会有一个默认的空参构造器,我们也可以通过给枚举变量参量来实现类的初始化。
private修饰符对于构造器是可以省略的,但这不代表构造器的权限是默认权限。
enum Color{ RED(1),GREEN(2),BLUE(3); private int code; Color(int code){ this.code=code; } public int getCode(){ return code; } }
- enum是如何工作的,就要对其进行反编译。使用枚举其实和使用静态类内部加载方法原理类似。枚举会被编译成如下形式:
public final class T extends Enum{}
枚举量的实现其实是public static final T 类型的未初始化变量,之后,会在静态代码中对枚举量进行初始化。所以,如果用枚举去实现一个单例,这样的加载时间其实有点类似于饿汉模式,并没有起到lazy-loading的作用。 - 对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。