一。饿汉模式
public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){ } private static Singleton getInstance(){ return instance; } }
第一次加载类时就会创建Singleton 实例,所以是线程安全的。另一方面,如果这个Singleton 实例的创建非常消耗系统资源,
而应用始终都没有使用Singleton 实例,那么创建Singleton 消耗的系统资源就被白白浪费了。
二。饱汉模式
public class Singleton{ private static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ if (instance == null){ instance = new Singleton(); } return instance; } }
线程不安全:两个线程A 和B 同时进入该方法的情形
1. A 进入if 判断,此时foo 为null,因此进入if 语句
2. B 进入if 判断,此时A 还没有创建foo,因此foo 也为null,因此B 也进入if 语句
3. A 创建了一个Foo 并返回
4. B 也创建了一个Foo 并返回
三。解决方法
1.同步方法
public static synchronized Singleton getInstance(){ if (instance == null){ instance = new Singleton(); } return instance; }
这种解决办法的确可以防止错误的出现,但是它却很影响性能:每次调用getInstance 方法的时候都必须获得
Singleton 的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了
2.同步块
public static Singleton getInstance(){ if(single == null){ synchronized (Singleton.class) { //保证了同一时间只能只能有一个对象访问此同步块 if(single == null){ single = new Singleton(); } } } return single; }
上述描述似乎已经解决了我们面临的所有问题,但从JVM 的角度讲,这些代码仍然可能发生错误。对于JVM 而言,它执行的是一个个Java 指令。
在Java 指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton()语句是分两步执行的。但是JVM 并不保证这两个操作的
先后顺序,也就是说有可能JVM 会为新的Singleton 实例分配空间,然后直接赋值给instance 成员,然后再去初始化这个Singleton 实例。这样就
可能出错,我们仍然以A、B 两个线程为例:
1. A、B 线程同时进入了第一个if 判断
2. A 首先进入synchronized 块,由于instance 为null,所以它执行instance = new Singleton();
3. 由于JVM 内部的优化机制,JVM 先画出了一些分配给Singleton 实例的空白内存,并赋值给instance成员(注意此时JVM 没有开始初始化这个实例)
然后A 离开了synchronized 块。
4. B 进入synchronized 块,由于instance 此时不是null,因此它马上离开了synchronized 块并将结果返回给调用该方法的程序。
5. 此时B 线程打算使用Singleton 实例,却发现它没有被初始化,于是错误发生了。
3.内部类
public class Singleton { private Singleton(){ } private class SingletonHoledr(){ private static Singleton instance = new Singleton(); } private static Singleton getInstance(){ return SingletonHoledr.instance; } }
单例与多线程