单例模式
单例模式:确保一个类最多只有一个实例,并提供一个全局访问点
有些对象我们只需要一个:线程池、缓存、硬件设备等
如果多个实例会有造成冲突、结果的不一致性等问题
单例模式的类图:
image由类图转化的代码
#Singleton.java
public class Singleton {
private static Singleton uniqeInstance=null;
//私有的构造方法,杜绝了在外界通过new来实例化对象
private Singleton(){
};
//在类方法中,专门用来构造Singleton对象。并将该单例对象暴露给外界
public static Singleton getInstance()
{
if(uniqeInstance==null)
{
uniqeInstance=new Singleton();
}
return uniqeInstance;
}
}
存在的问题: 在多线程的情况下,如果多个线程同时调用getInstance()方法,则有可能会创建多个实例对象
多线程下可能创建多个实例对象的解决方案
同步(synchronized)getInstance方法
private static Singleton uniqeInstance = null;
public synchronized static Singleton getInstance()
{
if(uniqeInstance==null)
{
uniqeInstance=new Singleton();
}
return uniqeInstance;
}
缺点: 每一次调用getInstance() 都会使用锁机制,会非常消耗资源。
“急切”创建实例(饿汉式)
静态成员变量不再为null,而是一开始就为它赋值一个实例
在这种情况下,类一被加载,静态成员变量就被初始化了。 如果程序中的单例对象全都采用这种方法,但某些单例对象从来没被使用过, 那么就会造成内存的浪费。
public class Singleton {
private static Singleton uniqeInstance=new Singleton();
//私有的构造方法,杜绝了在外界通过new来实例化对象
private Singleton(){
};
//在类方法中,专门用来构造Singleton对象
public synchronized static Singleton getInstance()
{
return uniqeInstance;
}
}
双重检查加锁(延迟加载)
public volatile static ChocolateFactory uniqueInstance = null;
public static ChocolateFactory getInstance() {
if (uniqueInstance == null) { //第一重检测
synchronized (ChocolateFactory.class) {
if (uniqueInstance == null) { //第二重检测
uniqueInstance = new ChocolateFactory();
}
}
}
return uniqueInstance;
}
- 如果第一次实例化时,有多个线程同时通过第一重检测,第二重检测保证了只能有一个线程实例化成功.其他竞争失败的线程直接返回 已创建的实例
- 此后所有的实例化经过第一重检测就直接返回
- volatile 存在的意义 (见下, 详细可参考并发编程与艺术)
-
上诉代码是一个错误的方案。在线程执行到第4行时,代码读到instance不为null时,instance引用的对象有可能还没有完成初始化
-
问题的根源
-
代码的第七行 instance = new Instance(); 创建了一个对象,这一行代码可以分解为如下3行伪代码
- memory = allocate(); //1. 分配对象的内存空间
- initInstance(memory) //2. 初始化对象
- instance = memory ; //3. 设置instance指向刚分配的内存地址
-
实际上,上面3行代码的2和3有可能发生重排序。 那么在多线程的情况下就有可能 访问到未初始化的对象!
- image
-
解决方案,使用volatile修饰成员变量instance。第2/3步将会被禁止重排序!
-
网友评论