单例模式介绍

SingleTon是指仅仅被实例化一次的类,通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统。

常见的写法如下:

1. 饿汉式

public class SingleTon{
    private SingleTon() {}
    private static final SingleTon sInstance = new SingleTon();
    public static SingleTon getInstance() {
        return sInstance;
    }
}

从代码中可以看到,这种模式在类加载时候就对实例进行创建,实例在整个程序周期都存在。好处是类加载时候创建一次实例,不会存在多个线程重复创建多个实例的情况,避免了多线程同步的问题。缺点也很明显,即使这个单例没有被用到也会被创建,比较浪费空间。

2. 懒汉式

public class SingleTon{
    private SingleTon() {}
    private static SingleTon sInstance;
    public static SingleTon getInstance() {
        if (sInstance == null) {
            sInstance = new SingleTon();
        }
        return sInstance;
    }
}

相比饿汉式,这种模式的好处是在使用的时候才会创建SingleTon的实例,避免了内存浪费,但是这种模式没有考虑到线程安全和反射导致的重复创建问题。修改代码如下:

public class SingleTon {
    private SingleTon() {}
    private static SingleTon sInstance;
    public synchronized static SingleTon getInstance() {
        if (sInstance == null) {
            sInstance = new SingleTon();
        }
        return sInstance;
    }
}

3. 双重校验锁

加了锁的单例模式虽然看起来既解决了多线程问题,又实现了滞后加载,但是依然存在着性能问题。synchronized方法会比一般方法慢,在系统多次调用的地方会产生性能问题。因此有了双重校验锁,实现方法如下:

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

我们看到双重校验锁即实现了延迟加载,又解决了线程并发问题,同时还解决了执行效率问题,是否真的就万无一失了呢?

这里要提到Java中的指令重排优化。所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。

这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

以上就是双重校验锁会失效的原因,不过还好在JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。代码如下:

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

4. 静态内部类

除了上面的方法,还有下面这种实现方法:

public class SingleTon{
    private SingleTon() {}
    private static class SingleTonHolder{
        public static SingleTon sInstance = new SingleTon();
    }

    public static SingleTon getInstance() {
        return SingleTonHolder.sInstance;
    }
}

这种方式同样利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

5. 枚举

从JDK1.5之后,可以使用如下方式实现SingleTon。

public enum SingleTon {
    INSTANCE;
    public void doSomeThing() {}
}

这种方法无偿地实现了序列化机制,即使在面对复杂的序列化情况和反射攻击的时候,也可以绝对防止多次实例化。虽然不常见,但是这个方法是实现单例的最佳方法。

6.防止反射攻击多次实例化

public class SingleTon {
    // 为什么要用静态变量?因为静态变量在内存中只有一个备份, 不像普通变量属于线程私有, 每个线程的私有内存中都有一个备份
    private static int sCount;
    private SingleTon() {
        synchronized (SingleTon.class) {
            if (sCount > 0) {
                throw new IllegalArgumentExecption("Can not create twice");
            }
            sCount++;
        }
    }
    ...
}

通过添加计数,可以防止通过反射的方式对单例多次实例化。

参考文章:http://blog.csdn.net/goodlixueyong/article/details/51935526

你可能感兴趣的:(单例模式介绍)