原来单例模式可以这样实现
2016-05-14 16:34
746 查看
单例模式是设计模式中最简单的一个模式,也是常被忽略的一个设计模式。如果不对单例模式进行一次认真的研究,真不见得能写出让自己满意的一种实现,即考虑岛安全和效率。单例模式有两种实现方式---饿汉式和懒汉式。
饿汉式单例模式就是在程序启动时就完成了初始化,这种实现比较简单
懒汉式单例模式是在第一次使用到单例对象来创建的,在单线程环境下下面的代码是ok的
但处在多线程环境下,这种实现就变得不安全了。假如有A,B两个线程同时执行4,instance == null 的结果都为true,所以A线程,B线程都会 new Singleton(); 就会导致两次创建了单例对象,如果有更多的线程同时访问,就可能不止创建了两次单例对象。为了防止多次创建单例对象,加锁对线程进行同步,所以就有了下面的代码
在getInstance方法上加上synchronized进行同步,这次不会肯定不会创建多次单例对象了。现在确实是线程安全了,但引进来了效率问题,因为同时只能有一个线程访问单例对象。只有第一个获得锁的线程才会去new Singleton(); 以后的线程只是读这个单例对象。多个线程不能同时读一个不变的单例对象显然效率还有进步的空间。就出现了著名的双重检查锁就是两次对instance == null判断,下面是双重检查锁的代码
完美了吧,当instance为null时,同时访问getInstance的线程都会判断instance==null,但同时只能有一个线程进入同步块,在同步块中第二次判断instance == null, 如果为null就会去创建单例对象,当单例对象创建好以后,以后的线程再访问单例对象时,就会直接return instance,再也不会进入同步块,这样多个线程就可以并发访问单例对象了,是不是很巧妙哈哈。仔细推敲,上面的代码还是有问题的,问题就出现在 instance = new Singleton(); 因为它不是一个原子操作,不是原子操作也就算了,关键java还会重排序,就是这个原因引发的问题。先看一下instance
= new Singleton(); 可以拆成的3行伪代码
如果按上面的执行顺序也是没有问题的,由于存在指令重排序,cpu实际执行的顺序可能是1, 3, 2。这样潜在的问题就出现了,在还没有对memory进行初始化之前,已经将instance 指向了memory,此时若有一个线程刚执行4,第一次判断instance是否为null,那么线程就会返回一个未完成初始化的instance。恶心的是2, 3可能会重排序,那就禁止这种重排序呗,看下面的代码
对没有看错,就是把instance生命为volatile变量即可。java内存模型会禁止对volatile变量的写与它之前的任何指令进行重排序。这样一个完美的单例模式就被实现出来了。除了双重检查锁,还有一种实现方式就是使用嵌套类,就是通过利用jvm对java类的初始化的同步来完成的,代码如下
饿汉式单例模式就是在程序启动时就完成了初始化,这种实现比较简单
//饿汉式 public class Singleton { private static Singleton instance = new Singleton(); //1,单例对象的引用 private Singleton() {} //2,声明构造函数私有 public static Singleton getInstance() { //3,获取单例对象方法 return instance; } }饿汉式中的单例对象instance是在对类Singleton初始化中创建的,在多线程环境下也是安全的。因为jvm在对类Singleton初始化时就考虑了多线程并发带来的问题。
懒汉式单例模式是在第一次使用到单例对象来创建的,在单线程环境下下面的代码是ok的
//懒汉式 public class Singleton { private static Singleton instance = null; //1,单例对象的引用 private Singleton() {} //2,声明构造函数私有 public static Singleton getInstance() { //3,获取单例对象方法 if (instance == null) { //4,判断是否为null instance = new Singleton(); //5,创建单例对象 } return instance; } }
但处在多线程环境下,这种实现就变得不安全了。假如有A,B两个线程同时执行4,instance == null 的结果都为true,所以A线程,B线程都会 new Singleton(); 就会导致两次创建了单例对象,如果有更多的线程同时访问,就可能不止创建了两次单例对象。为了防止多次创建单例对象,加锁对线程进行同步,所以就有了下面的代码
//懒汉式 public class Singleton { private static Singleton instance = null; //1,单例对象的引用 private Singleton() {} //2,声明构造函数私有 public static synchronized Singleton getInstance() { //3,获取单例对象方法 if (instance == null) { //4,判断是否为null instance = new Singleton(); //5,创建单例对象 } return instance; } }
在getInstance方法上加上synchronized进行同步,这次不会肯定不会创建多次单例对象了。现在确实是线程安全了,但引进来了效率问题,因为同时只能有一个线程访问单例对象。只有第一个获得锁的线程才会去new Singleton(); 以后的线程只是读这个单例对象。多个线程不能同时读一个不变的单例对象显然效率还有进步的空间。就出现了著名的双重检查锁就是两次对instance == null判断,下面是双重检查锁的代码
//懒汉式---双重检查锁 public class Singleton { private static Singleton instance = null; //1,单例对象的引用 private Singleton() {} //2,声明构造函数私有 public static Singleton getInstance() { //3,获取单例对象方法 if (instance == null) { //4,第一次判断instance是否为null synchronized (Singleton.class) {<span style="white-space:pre"> </span>//同步块 if (instance == null) {<span style="white-space:pre"> </span> //5<span style="font-family: Arial, Helvetica, sans-serif;">,第二次判断instance是否为null</span> instance = new Singleton();<span style="white-space:pre"> </span>//6<span style="font-family: Arial, Helvetica, sans-serif;">,创建单例对象</span> } } } return instance; } }
完美了吧,当instance为null时,同时访问getInstance的线程都会判断instance==null,但同时只能有一个线程进入同步块,在同步块中第二次判断instance == null, 如果为null就会去创建单例对象,当单例对象创建好以后,以后的线程再访问单例对象时,就会直接return instance,再也不会进入同步块,这样多个线程就可以并发访问单例对象了,是不是很巧妙哈哈。仔细推敲,上面的代码还是有问题的,问题就出现在 instance = new Singleton(); 因为它不是一个原子操作,不是原子操作也就算了,关键java还会重排序,就是这个原因引发的问题。先看一下instance
= new Singleton(); 可以拆成的3行伪代码
memory = allocate(); //1,分配对象的内存空间 ctorInstance(memory); //2,初始化对象 instance = memory; //3,设置instance指向刚才分配的内存空间
如果按上面的执行顺序也是没有问题的,由于存在指令重排序,cpu实际执行的顺序可能是1, 3, 2。这样潜在的问题就出现了,在还没有对memory进行初始化之前,已经将instance 指向了memory,此时若有一个线程刚执行4,第一次判断instance是否为null,那么线程就会返回一个未完成初始化的instance。恶心的是2, 3可能会重排序,那就禁止这种重排序呗,看下面的代码
//懒汉式---双重检查锁 public class Singleton { private static volatile Singleton instance = null; //1,单例对象的引用 private Singleton() {} //2,声明构造函数私有 public static Singleton getInstance() { //3,获取单例对象方法 if (instance == null) { //4,判断是否为null synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
对没有看错,就是把instance生命为volatile变量即可。java内存模型会禁止对volatile变量的写与它之前的任何指令进行重排序。这样一个完美的单例模式就被实现出来了。除了双重检查锁,还有一种实现方式就是使用嵌套类,就是通过利用jvm对java类的初始化的同步来完成的,代码如下
public class InstanceHoder { public static class Singleton { private static Singleton singleton = new Singleton(); public static Singleton getInstance() { return singleton; } } }
相关文章推荐
- Android ListView、GridView等性能优化
- Java获取泛型的实际类型
- 如何在CentOS 7中添加新磁盘而不用重启系统
- LR连接mysql数据库
- 第7周 C语言程序设计(新2版) 练习2-3 字符串转换成等价整型值
- 手把手教你将odex转dex
- java 内存:重排序
- XML解析方式
- WCF学习系列二---【WCF Interview Questions – Part 2 翻译系列】
- Palette(调色板)
- 将流转换成字符串
- 数据库设计三大范式
- excel 2003代码
- 选择云存储服务商的6条安全红线
- android开发笔记之多媒体—小图片的加载
- Problem E: Balance (华中农业大学预赛)
- 动作游戏设计 —— 敌人受击设计
- 基于dubbo框架下的RPC通讯协议性能测试 转
- 第八次作业
- 行内元素和块级元素的区别