您的位置:首页 > 其它

原来单例模式可以这样实现

2016-05-14 16:34 746 查看
单例模式是设计模式中最简单的一个模式,也是常被忽略的一个设计模式。如果不对单例模式进行一次认真的研究,真不见得能写出让自己满意的一种实现,即考虑岛安全和效率。单例模式有两种实现方式---饿汉式和懒汉式。

饿汉式单例模式就是在程序启动时就完成了初始化,这种实现比较简单

//饿汉式
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;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: