3. 【创建与销毁对象】用同步、静态内部类和枚举类型强化单例模式

本文是《Effective Java》读书笔记第3条。


单例模式,顾名思义,就是当你需要并且仅需要某个类只有一个实例的时候所采用的设计模式。

/**
 * 饿汉式单例模式
 */
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    // 静态工厂方法
    public Singleton getInstance() {
        return instance;
    }
}

如上,这就是一个单例模式,单例模式要保证达到如下基本目标:
1. 单例类只能有一个实例(static保证了这一点);
2. 单例类必须自己创建自己的唯一实例(private的构造方法);
3. 单例类必须给所有其他对象提供这一实例(例中只能由getInstance()方法得到单例对象)。

但是需要提醒一点:享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。

延迟创建对象

刚才的例子通常被叫做“饿汉式”单例模式,所谓“饿汉式”是相对于“懒汉式”来说的,“饿汉式”是在类加载之初就创建实例了的,而“懒汉式”是在使用的时候延迟创建实例对象的,这种方式在创建对象开销比较大时推荐使用:

/**
 * 懒汉式单例模式
 */
public class Singleton {

    private static Singleton instance;
    private Singleton() {}
    // 静态工厂方法
    public Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

同步

有经验的开发人员马上就会发现,这个代码不是线程安全的,并发环境下很可能出现多个Singleton实例。
解决方法1:最简单的方式就是给getInstance()方法增加synchronized关键字。

    public synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

但是每次调用getInstance()都要同步,而实际使用过程中除了创建对象的时候绝大部分情况下是不需要同步的,稍微改动一下,以便在已经存在单例对象后就不要同步了:

    public Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

静态内部类

虽然性能提高了些,但是代码貌似不太雅致啊,通常开发人员都有洁癖的,怎么受得了?试试静态内部类吧:

public class Singleton {

    private Singleton() {}

    private static class SingletonHolder {
        private final static Singleton INSTANCE = new Singleton();
    }

    public Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

因为加载Singleton类的时候,并不一定初始化静态内部类SingletonHolder,当调用getInstance()方法时才会加载SingletonHolder类,同时创建单例对象,所以其实是在懒汉式单例模式里边嵌套了饿汉式单例模式。这样既保证了线程安全,同时避免了同步带来的性能开销。

对于“饿汉式”和“懒汉式”的单例模式来说,创建过程分别在类加载时和实例对象使用时,如果类比较复杂,则需要注意根据不同的场景选择使用。

枚举类型

多线程的问题解决了,但是还存在其他问题,那就是当Singleton类在需要序列化的时候,为了维护并保证是单例的,必须声明所有的实例域都是transient的,并提供一个readResolve()方法,否则每次反序列化一个实例时都会创建一个新的实例。

    private Object readResolve() {
        return INSTANCE;
    }

具体原理在后续的读书笔记中会详细展开。
为了简化这个问题,在Java 1.5之后,就可以采用只包含单个元素的枚举类型的方式来实现单例。

public enum Singleton4 {
    INSTANCE;

    public void doSth() {}
}

这种方式最简洁,借助枚举的序列化机制,即使是在面对复杂的序列化或反射攻击的时候,也能够绝对防止多次实例化,几乎已经是实现Singleton的最佳方法了。

总结

通常来说,普通的应用环境下,“饿汉式”单例模式就可以满足要求,简单有效;当特别考虑需要采用延迟创建对象的场景的时候,建议采用静态内部类的单例模式;最后,单元素枚举类型虽然使用不多,但是在需要序列化的时候可以作为最佳单例模式的实现思路。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值