Java中的单例模式是一种常见的设计模式,单例模式主要分为懒汉式单例及饿汉式单例。单例模式的特点是,该类只有一个实例,对外的任何操作都只提供这一实例。
1、懒汉式单例
-
public class Singleton {
-
/**
-
* 私有构造方法
-
*/
-
private Singleton(){};
-
-
/**
-
* 静态全局变量
-
*/
-
private static Singleton instance = null;
-
-
/**
-
* 获取单例对象实例
-
* @return
-
*/
-
public static Singleton getInstance(){
-
if(instance == null){
-
instance = new Singleton();
-
}
-
return instance;
-
}
-
}
分析:以上程序为经典的单例模式创建方式,但该程序可能出现线程安全的问题,
例如当线程A调用getInstance()方法,首先判断instance是否为空,如果此时instance为空,则开始执行instance = new Singleton();假如此时线程B也调用了getInstance()方法,在A创建单例对象之前,B判断instance为空,则也开始创建单例对象,那么线程A和线程B就分别得到了两个不同的实例。
针对如上问题,我们对单例程序进行了如下改进:
-
public static synchronized Singleton getInstance(){
-
if(instance == null){
-
instance = new Singleton();
-
}
-
return instance;
-
}
获取单例对象的方法加上synchronized之后,确实可以保证线程安全,但如果这样每次getInstance()方法的调用都需要同步,从而影响程序的执行效率。
为此,我们继续进行程序的改进操作,使用双重检测加锁机制:
-
public static Singleton getInstance(){
-
if(instance == null){ //位置1
-
synchronized(Singleton.class){
-
if(instance == null){ //位置2
-
instance = new Singleton(); //位置3
-
}
-
}
-
}
-
return instance; //位置4
-
}
我们继续分析以上程序的弊端,由于JAVA内存模型中的“无序写”的机制导致了以上实现方式同样存在问题。Instance = new Singleton();该行语句JVM在执行时,其实分为两个步骤:一是创建一个对象,另外是赋值,但是这两个操作JVM是不保证顺序的,即有可能在对象完成创建之前,instance变量就已指向此实例。例如:线程A进入getInstance()方法,此时instance为空,则进入synchronized块。线程A在执行instance = new Singeton()时,把变量instance的值设置为一个非空值,但此时还没有执行new Singleton(),线程B在这个时候,也同样调用getInstance(),执行位置1处的判断,此时instance非空,然后返回了instance,而此时的instance只是一个还没有创建完成的对象的引用,从而可能导致了问题。当然这种情况发生的概率是比较低的。至于网上还有更NB的另外一种双重加锁机制扩展方法,个人认为其实是没有必要的。在此不再赘述,有兴趣的可以参考相关资料。
2、饿汉式单例
-
public class Singleton2 {
-
-
/**
-
* 私有构造方法
-
*/
-
private Singleton2(){};
-
-
/**
-
* 静态全局变量
-
*/
-
private static Singleton2 instance = new Singleton2();
-
-
/**
-
* 获取单例对象实例
-
* @return
-
*/
-
public static Singleton2 getInstance(){
-
return instance;
-
}
-
}
饿汉式单例是在程序加载是就创建出实例,从而避免了懒汉式单例在获取实例时加锁所造成的资源开销和线程安全问题,它的优点是线程安全,代码简洁,缺点是在类加载的时候就实例化,如果构造完成之后,单例占用资源比较大的情况下,对象迟迟不用,则会造成资源的浪费。
阅读(2520) | 评论(0) | 转发(1) |