在java中使用单例的类时,最简单的方法可以是:
public Class Singletin{
private static Singletin
instance = new Singletin();
public static Singletin
getInstance(){
return instance;
}
}
但是为了使用延迟初始化(不需要在应用启动时就初始化Singletin的instance变量,只是在第一次使用时初始化),经常看看到使用如下的方法:
public Class Singletin{
private static Singletin
instance = null;
public static Singletin
getInstance(){
if(instance == null){
synchronized (Singletin.class){
if(instance ==
null){ instance = new Singletin();
}
}
}
return instance;
}
}
看起来这是一个不错的方法,但是实际上呢?让我们来看看java的内存模型
在多线程环境中,每一个线程有自己的本地内存(local memory),
synchronized进行的操作并不仅仅是用信号量机制进行加锁,还促使线程的本地内存和主内存进行数据同步memory
barrier -- a forced synchronization between the thread's local
memory and main
memory.:进入synchronized区域时,把主内存的变量值读入本地内存,而退出synchronized区域时,把本地内存的变量值写入主
内存。
instance = new Singletin();执行的操作有三步:分配内存,
调用构造函数初始化(初始化各个属性),instance指向分配的内存。但是这个顺序不是固定的,有可能是:分配内存,
instance指向分配的内存,调用构造函数初始化(初始化各个属性)。如果在执行到第二步后,另外一个线程进入getInstance函数,首先判断
instance!=null,则直接返回该没有初始化的内存对象,后续的使用就会产生问题。可能有人会说不是退出synchronized的时候才会进
行数据同步吗?这样就不会产生刚刚分配内存,还没有初始化的问题。但是进行数据同步也不是原子的操作,也会同样存在问题。另外由于处理器对内存的
cache也回存在问题。总之导致产生double-checked
locking的问题很多,因此很多方法对某一种情况可以避免,但是对另一种情况就不能避免。
在jdk5之后(包含JDK5)之后,可以使用volatile限制来定义instance,能够避免double-checked
locking.
public Class Singletin{
private volatile static
Singletin instance = null;
public static Singletin
getInstance(){
if(instance == null){
synchronized (Singletin.class){
if(instance ==
null){ instance = new Singletin();
}
}
}
return instance;
}
}
另外一篇很好的分析文章:
http://blog.csdn.net/ralph623/article/details/471026
http://www.blogjava.net/Jhonney/archive/2011/04/08/110280.html