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