读书人

单例模式与线程保险

发布时间: 2012-09-18 16:21:42 作者: rapoo

单例模式与线程安全

请看如下的单例类:

class Singleton{  private static Singleton singleton = null;  public static Singleton getSingleton() {    if (null == singleton) {      singleton = new Singleton();    }    return singleton;  }

?首先判断singleton是否为null,如果是就创建singleton对象,否则直接返回singleton。但是判断和创建并非原子操作,假设线程1正在执行null == singleton,判断为true,准备执行下一句new Singleton();此时线程2可能已经new了一个 Singleton了,线程1再次new了一个Singleton,出现2个Singleton与单例的设计思想不符,即单例的控制在并发情况下失效了,测试代码可直观的反应该问题:

public class MyThread extends Thread {  public void run() {    System.out.println(Singleton.getSingleton().toString());  }  public static void main(String[] args) {    for(int i=0;i<10;i++){      MyThread myThread = new MyThread();      myThread.start();    }  }}class Singleton{  private static Singleton singleton = null;  public static Singleton getSingleton() {    if (null == singleton) {      singleton = new Singleton();    }    return singleton;  }}

?输出:

Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@173a10f
Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@69b332

?

可以在getSingleton方法前加synchronized,确保任意时刻都只有一个线程可以进入该方法。但是这样一来,会降低整个访问的速度,而且每次都要判断。

可以使用"双重检查加锁"的方式来实现,就可以既实现线程安全,又能够使性能不受到很大的影响。

    public class Singleton {          /**           * 对保存实例的变量添加volatile的修饰           */          private volatile static Singleton instance = null;          private Singleton(){          }          public static  Singleton getInstance(){              //先检查实例是否存在,如果不存在才进入下面的同步块              if(instance == null){                  //同步块,线程安全地创建实例                  synchronized(Singleton.class){                      //再次检查实例是否存在,如果不存在才真正地创建实例                      if(instance == null){                          instance = new Singleton();                      }                  }              }              return instance;          }      } 
?

所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

双重检查机制,尽可能缩小了同步块的范围。

这里解释一下为什么要判断2次,当两个线程调用getInstance方法时,它们都将通过第一重instance==null的判断,由于同步机制,这2个线程只能有一个进入,另一个排队。而此时如果没有第二重判断,则第一个线程创建了实例,而第二个实例离开队列重新获得锁,则将继续创建实例,这样就没有达到单例的目的。

?

在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:

由静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据时

访问 final 字段时

在创建线程之前创建对象时

线程可以看见它将要处理的对象时。

?

一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。

看看代码示例可能会更清晰一些,示例代码如下:

    public class Singleton {          /**           * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例           * 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载           */          private static class SingletonHolder{              /**               * 静态初始化器,由JVM来保证线程安全               */              private static Singleton instance = new Singleton();          }          /**           * 私有化构造方法           */          private Singleton(){          }          public static  Singleton getInstance(){              return SingletonHolder.instance;          }      } 
?

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本

?

?

?

读书人网 >编程

热点推荐