一次搞定 单例模式

单例模式 大家都不陌生、但是写出一个让人满意的单例 并不是那么容易的。
从另一个角度来分析单例模式 方便大家理解。

单例模式:只能创建出类的一个对象 也是唯一的一个对象。
怎么保障只能创建出一个类对象呢?
  首先想到的是静态 静态对象是属于类的。所以能保证静态对象的唯一。
  其次屏蔽类自身具有的构造方法。 这样就能屏蔽这种创建类对象的方式。
       类名 对象名 =  new 类名()
 
第一:屏蔽构造方法,设置成private的。  下面是外部类使用构造方法创建对象的报错信息
SingletonX singletonTest = new SingletonX();
【The constructor SingletonX() is not visible】
第二:自己内部new出一个 自己的对象唯一的对象 返回。

=============================下面是V1.0版本================================
public class SingletonX {
//私有化的构造方法,保证外部的类不能通过构造器来实例化 
private SingletonX() {}
//单例   实例变量
private static SingletonX singleton = null;
//获取单例对象实例   对外提供
public static SingletonX getInstance(){
if(singleton==null)
singleton = new SingletonX();
                                  return singleton;
}
}

=============================下面是V2.0版本================================
上面的代码在多线程的环境中不是线程安全的。
等当两个线程进入if (instance == null)时,两个线程都判断instance为空,接下来就会得到两个实例了。这不是我们想要的单例。
为了保证线程安全。使用synchronized  修饰一下
public static synchronized  SingletonX getInstance(){
}

此方法是可以解决问题。但是仔细想一想 其实只需要在第一次调用getInstance()的时候加锁就可以了。这样把synchronized  加在方法上会导致每次调用getInstance方法。
把synchronized  加到代码块上试一试吧
=============================下面是V2.5版本================================
//获取单例对象实例   对外提供
public static SingletonX getInstance() { 
         if (instance == null) {  
             synchronized (Singleton.class) {
             //再次判断是否为空 防止上面多线程的时候有多个线程进入了等待synchronized 如不再次进行为空判断依然会出现多个实例。
                 if (instance == null) {  
                     instance = new Singleton();  
                } 
             } 
         }
         return singleton;

=============================下面是V2.5版本================================
不过又听说java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。

instance = new Singleton(); 这行其实做了两个事情:
1、调用构造方法,创建了一个实例。
2、把这个实例赋值给instance这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。 
synchronized 能保障同一时间只有一个线程进入同步块。但是不能保证每次都能完整执行同步块,也就是执行了一部分就退出,让给其他线程来执行。  A线程执行了上面说的第2步退出
此时instance 不为空了,我也不知道它现在是一个什么值? B线程进来判断不为空 返回了一个没有调用构造方法实例出来的一个值。紧接着A线程继续调用构造方法返回真正的单例对象。 


解决方案:
就是要保障instance = new Singleton() 这个搞成原子性的。 引入一个Temp变量来保存创建出来的实例对象,如果没有完整创建,退出。B线程进来instance 依然是Null。

//获取单例对象实例
public static Singleton getInstance() {
     if (instance == null) {
         synchronized (Singleton.class) {          //1
             Singleton temp = instance;            //2
             if (temp == null) {
                 synchronized (Singleton.class) {  //3
                     temp = new Singleton();   //4   
                 }
                 instance = temp;                  //5     
             }
         }
     }
     System.out.println("我是解决无序写懒汉式单例!");
     return instance; 
}

=============================下面是V3.0版本================================

虽然实现了但是很乱,精简一下代码换个思路。
//饿汉式
  public class Singleton {     
      //单例变量 ,static的,在类加载时进行初始化一次,保证线程安全
      private static Singleton instance = new Singleton();   
     
      //私有化的构造方法,保证外部的类不能通过构造器来实例化。    
      private Singleton() {}
     
      //获取单例对象实例    
      public static Singleton getInstance() {
          System.out.println("我是饿汉式单例!");
          return instance;
      }
  }

代码分析:饿汉式单例,意思就是预先声明Singleton对象,这样带来的一个缺点就是:如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。

==========继续改进调用getInstance方法时再进行构造方法初始化对V4.0===========
  //内部类实现懒汉式
  public class Singleton {     
      private static class SingletonHolder{
          //单例变量 
          private static Singleton instance = new Singleton();
      }
     
      //私有化的构造方法,保证外部的类不能通过构造器来实例化。
      private Singleton() {}     
      //获取单例对象实例
      public static Singleton getInstance() {
          System.out.println("我是内部类单例!");
          return SingletonHolder.instance;
      }
  }

懒汉式(避免上面的资源浪费)、线程安全、代码简单。因为java机制规定,
内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),
而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance

SingletonHolder是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。


欢迎探讨:QQ 350355139 设计模式一次搞定

参考资料:
http://blog.163.com/yanjingyu_happy/blog/static/111639566201364115047132/

http://devbean.blog.51cto.com/448512/203501/

猜你喜欢

转载自ican.iteye.com/blog/2249235