java 双重检查单例和静态内部类单例

最近在看imageLoader 源码的时候,看到单例采用的双重检查机制,但是在看其他大牛写的代码的时候,采用的是静态的内部类作为单例,在此研究下。


下面是单例的相关内容:

  • 懒汉式
//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  

    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}

懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟.但是存在一个问题就是线程不安全的。

  • 何为线程安全

java中的线程安全是什么:
就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问。

什么叫线程安全:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

  • 饿汉式
  //饿汉式单例类.在类初始化时,已经自行实例化   
public class Singleton1 {  
    private Singleton1() {}  
    private static final Singleton1 single = new Singleton1();  
    //静态工厂方法   
    public static Singleton1 getInstance() {  
        return single;  
    }  
} 

饿汉式本身就是线程安全的,饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。

  • 双重检查锁定

java 双重检查单例和静态内部类单例_第1张图片

这个是imageloader 里面创建单例使用的源代码,他采用的就是:在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。

这里有一个关键字我们需要注意 :volatitle

注意:假设没有关键字volatile的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行instance = new Instance(),该构造方法是一个非原子操作,编译后生成多条字节码指令,由于JAVA的指令重排序,可能会先执行instance的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后instance便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程B进入,就会看到一个不为空的但是不完整(没有完成初始化)的Instance对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。

  • 静态内部类的方式
//采用静态内部类的方式,作为单例,直接用classLoader(jVM 类加载机制)进行处理异步加锁的问题,并减小内存消耗
    private static class SingletonHolder {
        private static final RemoteApiHelper INSTANCE = new RemoteApiHelper();
    }

    public static RemoteApiHelper getInstance() {
        return SingletonHolder.INSTANCE;
    }

摘录:http://blog.sina.com.cn/s/blog_6d2890600101gb8x.html,对静态内部类的解释

这个解决方案被称为Lazy initialization
holder class 模式,这个模式综合使用了java的类级内部类和多线程缺省同步锁的知识,
很巧妙的同时实现了延迟加载和线程安全。

1 相应的基础知识
(1)什么是类级内部类?
简单点说,类级内部类指的是,有static修饰的成员内部类。如果没有static修饰的成员式内
部类被称为对象级内部类。
(2)类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此
可以直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
(3)类级内部类中,可以定义静态的方法。在静态方法中只能引用外部类中的静态成员方法或变量。
(4)类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。

多线程缺省同步锁的知识:
大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制,
但是在某些情况下,JVM已经隐含的为您执行了同步,这些情况下就不用自己再来进行同步控制了。
这些情况包括:
(1)由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
(2)访问final字段时
(3)在创建线程之前创建对象时
(4)线程可以看见它将要处理的对象时

2 解决方案的思路
(1)要想很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。
(2) 如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类, 那就不会创建对象实例,从而同步实现延迟加载和线程安全。

3.补充说明下他是如何体现 懒加载的(Lazy initialization):

(1)因为内部静态类是要在有引用了以后才会装载到内存的。所以在你第一次调用getInstance()之前,SingletonHolder是没有被装载进来的,只有在你第一次调用了getInstance()之后,里面涉及到了return SingletonHolder.instance; 产生了对SingletonHolder的引用,内部静态类的实例才会真正装载。这也就是懒加载的意思

  • 关于 JVM来保证线程的安全性 这句话的意思:

利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗。感觉这句话等于没说,下面我在看classloader 机制的网站,希望可以一起学习进步:

classloader机制解析
http://my.oschina.net/aminqiao/blog/262601?fromerr=vGSmHfhc

你可能感兴趣的:(java,学习日记)