单例模式---Singleton

来源:互联网 发布:淘宝卖家不评论吗 编辑:程序博客网 时间:2024/05/19 12:28

简单的Singleton

public class SingletonClass {  private static final SingletonClass instance = new SingletonClass();  public static SingletonClass getInstance() {    return instance;  }  private SingletonClass() {  }}

一个私有构造器,一个Singleton类型的属性,对应一个SingletonClass对象的实例,只能通过getInstance()方法获得,保证只有一个对象存在。

性能优化:
static属性不论类是否被调用都会被初始化,如果这个类一直不会被调用那么性能是浪费的。

解决:

public class SingletonClass {  private static SingletonClass instance = null;  public static SingletonClass getInstance() {    if(instance == null) {      instance = new SingletonClass();    }    return instance;  }  private SingletonClass() {  }}

把instan初始化为null,第一次使用的时候判断是否为null来决定创建对象,所以不能为final。
这个过程为lazy loaded,迟加载,直到使用的时候才加载。

同步怎么办?

如果线程A希望使用getInstance()方法,对于A来说intance是null的,这是A开始创建实例,但如果这时CUP时间片切换,线程B开始执行,这时A还没有来得及new instance,那么B会开始 创建,之后A继续执行,但是A不会在判断一次,A会直接new instance,那么现在A,B拥有不同的SingletonClass instance。

解决:加锁

public class SingletonClass{    private static SingletonClass instance=null;    public synchronized static SingletonClass getInstance(){     if(instance == null) {      instance = new SingletonClass();        }    }    private SingletonClass(){    }}

getInstance()方法加上了锁,另外一个线程只能等现有线程结束才能使用这个方法。

继续性能:
synchronized修饰的同步块非常慢,如果多次调用getInstance()方法,就要考虑性能。

我们为什么要加锁,是因为if()与new的操作分离了,如果这两个操作能够原子的进行呢?

public class SingletonClass {  private static SingletonClass instance = null;  public static SingletonClass getInstance() {    synchronized (SingletonClass.class) {      if(instance == null) {        instance = new SingletonClass();      }    }        return instance;  }  private SingletonClass() {  }}

这样视乎并没有解决问题,每次调用getInstance() 方法必然还要同步,如果事先判断null在同步呢?

public class SingletonClass {  private static SingletonClass instance = null;  public static SingletonClass getInstance() {    if(instance == null) {    synchronized (SingletonClass.class) {      if(instance == null) {        instance = new SingletonClass();      }     }     }       return instance;  }  private SingletonClass() {  }}

到这视乎都没什么问题。

但如果A线程开始run,new SingletonClass的instance,此时时间片切换,线程B开始run,先判断instance是否为空,这时A已经指向了instance那块内存,但是还没有调用构造方法,所以B判断不为null,于是把instance返回,但这时instance还没有构造完成,bug。

解决:

public class SingletonClass {  private static SingletonClass instance = null;  public static SingletonClass getInstance() {    if (instance == null) {      SingletonClass sc;      synchronized (SingletonClass.class) {        sc = instance;        if (sc == null) {          synchronized (SingletonClass.class) {            if(sc == null) {              sc = new SingletonClass();            }          }          instance = sc;        }      }    }    return instance;  }  private SingletonClass() {  }}

我们在第一个同步块里面创建一个临时变量,然后使用这个临时变量进行对象的创建,并且在最后把instance指针临时变量的内存空间。写出这种代码基于以下思想,即synchronized会起到一个代码屏蔽的作用,同步块里面的代码和外部的代码没有联系。因此,在外部的同步块里面对临时变量sc进行操作并不影响instance,所以外部类在instance=sc;之前检测instance的时候,结果instance依然是null。

不过,这种想法完全是错误的!同步块的释放保证在此之前——也就是同步块里面——的操作必须完成,但是并不保证同步块之后的操作不能因编译器优化而调换到同步块结束之前进行。因此,编译器完全可以把instance=sc;这句移到内部同步块里面执行。这样,程序又是错误的了!

解决:

volatile关键字。被volatile修饰的写变量不能和之前的读写代码调整,读变量不能和之后的读写代码调整!因此,只要我们简单的把instance加上volatile关键字就可以了。

public class SingletonClass {  private volatile static SingletonClass instance = null;  public static SingletonClass getInstance() {    if (instance == null) {      synchronized (SingletonClass.class) {        if(instance == null) {          instance = new SingletonClass();        }      }    }    return instance;  }  private SingletonClass() {  }}

另外一种:

public class SingletonClass {  private static class SingletonClassInstance {    private static final SingletonClass instance = new SingletonClass();  }  public static SingletonClass getInstance() {    return SingletonClassInstance.instance;  }  private SingletonClass() {  }}

使用了Java的静态内部类。因为SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次。

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

0 0
原创粉丝点击