java单例模式的几种不同实现
2017-09-20 23:31
267 查看
懒汉式,线程不安全
当被问到要实现一个单例模式时,很多人的第一反应是写出如下的代码,包括教科书上也是这样教我们的。public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }} |
懒汉式,线程安全
为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance;} |
双重检验锁
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的
if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public static Singleton getSingleton() { if (instance == null) { //Single Checked synchronized (Singleton.class) { if (instance == null) { //Double Checked instance = new Singleton(); } } } return instance ;} |
instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3
件事情。
给 instance 分配内存
调用 Singleton 的构造函数来初始化成员变量
将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
我们只需要将 instance 变量声明成 volatile 就可以了。
public class Singleton { private volatile static Singleton instance; //声明成 volatile private Singleton (){} public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } |
1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。
相信你不会喜欢这种复杂又隐含问题的方式,当然我们有更好的实现线程安全的单例模式的办法。
饿汉式
static final field
这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。public class Singleton{ //类加载时就初始化 private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; }} |
静态内部类
static nested class
我比较倾向于使用静态内部类的方法,这种方法也是《Effective Java》上所推荐的。public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } |
枚举
Enum
用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。public enum EasySingleton{ INSTANCE;} |
总结
一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。上述所说都是线程安全的实现,文章开头给出的第一种方法不算正确的写法。就我个人而言,一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。
相关文章推荐
- Java单例模式几种实现方式
- javascript中数组Array对象调用不同的方法可以实现几种数据结构
- 在Hibernate里面动态切换SChema实现访问不同的数据库的几种方法
- 2. 缓存的几种不同的实现方法?
- 不同数据源之间数据同步的几种实现方式
- 遗传算法中几种不同选择算子及Python实现
- [转]java单例模式的几种实现
- JAVA单例模式的几种实现方法
- 谈谈下订单的几种实现方式(用不同的模式实现:装饰器模式、代理模式、命令模式、状态模式、模版模式)
- 实现业务逻辑的几种不同方法,及其优缺点 事务脚本、表模块、活动记录、领域模型
- 【笔试题】不同页面之间实现参数传递的几种方式
- Android开发必知--几种不同对话框的实现
- JAVA单例模式的几种实现方法
- JAVA单例模式的几种实现方法
- [转]几种不同语言版本的HMM实现
- JAVA单例模式的几种实现方法
- 多线程有几种实现方法?同步有几种实现方法?静态同步方法和非静态有何不同?—— Java经典面试题系列
- Java单例模式的几种实现方式
- Java单例模式实现的几种方式
- android几种不同对话框的实现方式