3. 【创建与销毁对象】用同步、静态内部类和枚举类型强化单例模式
2016-01-04 22:47
323 查看
本文是《Effective Java》读书笔记第3条。
单例模式,顾名思义,就是当你需要并且仅需要某个类只有一个实例的时候所采用的设计模式。
如上,这就是一个单例模式,单例模式要保证达到如下基本目标:
1. 单例类只能有一个实例(static保证了这一点);
2. 单例类必须自己创建自己的唯一实例(private的构造方法);
3. 单例类必须给所有其他对象提供这一实例(例中只能由getInstance()方法得到单例对象)。
但是需要提醒一点:享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
解决方法1:最简单的方式就是给
但是每次调用
因为加载
对于“饿汉式”和“懒汉式”的单例模式来说,创建过程分别在类加载时和实例对象使用时,如果类比较复杂,则需要注意根据不同的场景选择使用。
具体原理在后续的读书笔记中会详细展开。
为了简化这个问题,在Java 1.5之后,就可以采用只包含单个元素的枚举类型的方式来实现单例。
这种方式最简洁,借助枚举的序列化机制,即使是在面对复杂的序列化或反射攻击的时候,也能够绝对防止多次实例化,几乎已经是实现Singleton的最佳方法了。
单例模式,顾名思义,就是当你需要并且仅需要某个类只有一个实例的时候所采用的设计模式。
/** * 饿汉式单例模式 */ 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的最佳方法了。
总结
通常来说,普通的应用环境下,“饿汉式”单例模式就可以满足要求,简单有效;当特别考虑需要采用延迟创建对象的场景的时候,建议采用静态内部类的单例模式;最后,单元素枚举类型虽然使用不多,但是在需要序列化的时候可以作为最佳单例模式的实现思路。相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树