Effective Java之用私有构造器或者枚举类型强化Singleton属性(三)

来源:互联网 发布:mac 开发php 必备软件 编辑:程序博客网 时间:2024/06/06 04:52

1.饿汉式加载

类加载时就创建

public class MaYun {private static Mayun instance = new Mayun();private static getInstance() {return instance;}private MaYun() {//MaYun诞生要做的事情}public void splitAlipay() {System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);}}

2.懒汉式加载

类使用时加载

public class MaYun {private static MaYun instance = null;private MaYun() {//MaYun诞生要做的事情}public static synchronized MaYun getInstance() {if (instance == null) {instance = new MaYun();}return instance;}public void splitAlipay() {System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);}}

3.懒汉式的线程安全版(双重检验锁)

public class MaYun {private volatile static MaYun instance;private MaYun (){}public static MaYun getInstance() {if (instance == null) {synchronized (MaYun.class) {if (instance == null) {instance = new MaYun();}}}return instance;}}

在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

4.静态内部类(推荐)

public class MaYun {private static class SigletonHolder {private static final instance = new MaYun();}public static final getInstance() {return SigletonHolder.instance;}private MaYun() {//MaYun诞生要做的事情}public void splitAlipay() {System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);}Call:MaYun.getInstance().splitAlipay();

加载MaYun类时,在类的加载阶段把静态内部类加载了,也就是利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以这种方法非常推荐。

5.编写一个包含单个元素的枚举类型(极力推荐)

public enum MaYun {himself; //定义一个枚举的元素,就代表MaYun的一个实例private String anotherField;MaYun() {//MaYun诞生要做的事情//这个方法也可以去掉。将构造时候需要做的事情放在instance赋值的时候:/** himself = MaYun() {* //MaYun诞生要做的事情* }**/}public void splitAlipay() {System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);}}Call:MaYun.himself.splitAlipay();

我们来认真分析一下这5种方法:

  1. 1234方法把构造器声明成私有的,但是这种方法是不能抵抗反射机制的攻击的!如下:
Constructor<?> constructor = MaYun.class.getDeclaredConstructors()[0];constructor.setAccessible(true);Elvis instance = (Elvis) constructor.newInstance();

依然可以创建一个对象!
解决的方法是添加计数器,大于1时抛出错误:

public class MaYun {    private static AtomicInteger count = new AtomicInteger(0);    private static final MaYun INSTANCE = new Elvis();    private MaYun() {        if(count > 0) {            throw new IllegalArgumentException("Cannot create Elvis twice");        }        count.incrementAndGet()    }

而方法5美剧方法可以防止反射攻击,当你试图通过反射去实例化一个枚举类型的时候会抛出IllegalArgumentException异常:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects    at java.lang.reflect.Constructor.newInstance(Constructor.java:416)    at org.effectivejava.examples.chapter02.item03.enumoration.MaYun.main(MaYun.java:21

从这里看,方法5具有绝对的优势!

2.如果上面实现的Singleton是可以序列化的。那么方法1234加上implements Serializable只保证它可以序列化,为了保证反序列化的时候,实例还是Singleton,必须声明所有的实例域都是transient的,并且提供 readResolve方法,否则,每次反序列化都会生成新的实例。

但是方法5无偿提供了序列化机制,绝对防止多次实例化,又是绝对的优势!

这就是方法5值得极力推荐的的原因,同时jdk1.5以上才能使用的方法但是这种方法用的人太少了!!所以改变从你我开始!

阅读全文
0 0
原创粉丝点击