现在的位置: 首页 > 综合 > 正文

设计模式–单件模式

2019年06月12日 ⁄ 综合 ⁄ 共 2154字 ⁄ 字号 评论关闭

2015年1月29日16:02:59

单件模式是用来保证共有资源唯一性的设计模式,举个例子吧,假如我们是艺人,我们在同一个经纪人的协助下开展工作,我们可以委托这个经纪人做一些事情,但是我们不能委托经纪人在帮你做报表的同时帮我订外卖。用程序猿的语言来表达就是,我们可能在多处地方持有对同一资源的引用。由于资源一半都是用类来封装,比如打印机,我们通过打印机驱动的类来使用打印机资源,所以我们的问题转化为保证类的实例对象的唯一性。

这样 我们就面临了两个选择

1系统持有一个全局对象

2使用单件模式

上述的方案一显然不可行,原因1增加了全局对象的攻击窗口,在系统的所有地方都可以访问这个共有资源,这样出错的可能性大大增加,并且修复bug的难度也翻倍了

2消耗系统资源,全局对象在系统的生命周期里都占有资源,消耗资源很多。(3全局对象是归JVM管理的,可能存在创建问题,这点是书上写的,我没能理解)

上面第一个是关键,第二点可以用延迟创建来缓解一下资源消耗问题。

那么接下来的问题就是怎么使用单件模式。

一种常用的方案是私有化构造器,然后公开一个获取实例的静态方法

public class Singleton{
private static Singleton  singletonObject;
private Singleton(){ } 
public static Singleton getInstance(){ ...... }
}


私有化的构造器避免了程序员在类外部实例化对象,我们在类内存一个静态的对象,我们之后获取的对象就是存储的这个静态对象。

这样问题来了,这不是和之前说的全局对象不是很像么,但是注意这个对象是JVM管理的,对于程序来说我们在不使用的时候并没有存对这样一个对象的引用,也就是没有那么大的攻击窗口。

另一外需要讨论的问题是静态对象怎么创建

1需要的时候创建

 public static Singleton getInstance(){
if(singletonObject == null){ singletonObject = new Singleton();}
return singletonObject;
} 


这属于延迟创建的方法,也就是在首次需要用的时候类才被创建

但是这样的方法在多线程的时候可能会出现问题,比如现在有2个线程同时需要获取这个对象,线程A判断对象为空,此时切换到B线程,线程B判断对象为空,然后切换到A线程,A线程新建对象,A线程返回对象,此时切换回B线程,B线程新建对象,B线程 返回对象。这样程序同时出现了2个单件对象,这样明显是错的

于是程序需要改进

改进方案一

 public synchronized static Singleton getInstance(){

if(singletonObject == null){
   singletonObject   = new Singleton();
}
return singletonObject;
}

用同步关键字限制同时只有1个线程能使用这个方法,保证这个方法的原子性,这是一个解决方案,但是存在效率损失的问题,这样的方案在对象获取的频率比较低的时候适用

改进方案二

在创建对象的时候进入同步区

public static Singleton getInstance(){

if(singletonObject == null){
synchronized(Singleton.class){
<pre name="code" class="java">if(singletonObject == null){
 singletonObject = new Singleton();
}
}
}
return singletonObject;
}






对于多线程的时候也存在一个需要注意的问题,里面存在是每个程序可能有自己的内存对象备份,所以在实例同步的时候需要对需要加锁的对象添加volatile关键字

详细参见volatile的解析http://sakyone.iteye.com/blog/668091        这个应该是与操作系统中同步的metrux变量类似

   private volatile static Singleton  singletonObject;

这样最小限度的减少了同步的损耗

我在查阅资料的时候发现一份这样的文档http://article.yeeyan.org/view/213582/191888

里面提出的核心问题是多线程对对象的实例化可能存在问题, 参见C++的对象实例化,对象实例化分分配内存和对象初始化2部分,在双重锁定的时候,在分配内存的时候就可能返回了锁,这样第二线程可能返回一个未初始化变量的对象。(虽然我还是没有完全接受,但是还是列出来,希望大神指点一下)

2提前声明

   private static Singleton  singletonObject= new Singleton();

这样的方案避免了之前所说的所有同步问题,也只有在类初次调入内存的时候才会创建静态对象,总的来说是最好的方案。

3用工厂模式创建

这个方案是将创建对象的职责从JVM拿到工厂之中,内部实现可以参见之前讨论的两点,在遇到多线程的时候 还是采用提前声明的方案。

工厂可以采取全局变量的方式实现,这样虽然内存消耗存在,但是减少了攻击窗口。

当然工厂也可以用单件模式实现,不过看起来就有点变扭。

抱歉!评论已关闭.