单例常见实现方式
- 饿汉式:在类被加载时就初始化单例
- 懒汉式:在需要的地方才初始化单例
饿汉式加载问题
传统的在类加载时就创建单例的方法存在一些问题:
1
2
3
4
5
6
7
8
9
10
|
class SingleInstance {
private SingleInstance() {...}
private static SingleInstance mInstance = new SingleInstance();
public static SingleInstance getInstance(){
return mInstance;
}
}
|
如果类的实例化含义太多的操作,就可能会影响程序的性能,启动时间等。而且如果单例在程序中并未用到,则会一直存在一个无法被GC回收的对象,造成浪费。
如果单例一定会使用,而且构造方法中又没有太多操作,则可以在类加载时就初始化单例。
懒汉式加载问题
下面这种懒加载,在多线程情况下可能会造成创建了多个单例。
线程A正在执行new
操作,线程B同时来获取单例,此时instance
还是null
,线程B就也会去执行new
操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class SingleInstance {
private SingleInstance() {}
private static SingleInstance mInstance;
public static SingleInstance getmInstance() {
if (null == mInstance) {
mInstance = new SingleInstance();
}
return mInstance;
}
}
|
使用synchronized
优化懒加载
1
2
3
4
5
6
|
public synchronized static SingleInstance getInstance(){
if(null==mInstance){
mInstance = new SingleInstance();
}
return mInstance;
}
|
这种方式加锁,会造成性能问题。synchronized
修饰了方法,所有线程获取单例时都会经历同步锁,不论单例是否已经被创建。而且获取单例可能是个频繁的操作,直接对方法加锁会降低性能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private volatile static SingleInstance mInstance;
public static SingleInstance getInstance() {
// 避免在 mInstance 初始化之后, 多个线程每次都获取同步锁
if (null == mInstance) {
synchronized (SingleInstance.class) {
// JVM 可能存在指令重排序, 先为对象分配内存并赋值给引用返回, 后进行初始化
if(null == mInstance) {
mInstance = new SingleInstance();
}
}
}
return mInstance;
}
|
推荐这种方式实现单例懒加载,只当单例没有被构造时才会进入加锁的部分。而且使用volatitle
关键字, 禁止对 volatile
修饰的变量进行指令重排序, 且 volatitle
会让每个线程每次都从主内存获取最新的变量值, 确保了变量在多线程下的可见性.
使用静态内部类优化懒加载
静态内部类(Holder
)只有在被用到时才会被加载, 加载过程由 JVM 保证多线程安全, 而且静态变量的赋值只会在类加载时执行一次. 后续在获取单例时因为内部类(Holder
)已经加载过了所以不会再走赋值过程, 直接返回静态变量(mInstance
).
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class SingleInstance {
private SingleInstance() {}
public static SingleInstance getInstance() {
return Holder.mInstance;
}
private static class Holder {
private static final SingleInstance mInstance = new SingleInstance();
}
}
|