Spring温故知新(三)singleton单例模式

本来今天打算介绍一下Spring的IoC容器,但是开了一早上的会感觉时间有点紧,今天写有点够呛。再加上看到昨天的访客大部分都对设计模式比较感兴趣,那么我就先提前介绍一下设计模式里也是比较重要的singleton单例模式。

单例模式之所以提前在这里介绍,是由于Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方。

那么什么是单例模式呢?我先引用一下别的地方抄来的理论介绍:
[quote]单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。[/quote]

这个理论介绍其实还是比较通俗易懂的,这里沿用我前面一篇IoC部分的造机器人的例子来解释。
之前我们造机器人去向邻居打招呼,每次都是新造的一个机器人,向不同的邻居、或者不同的时间向同一个邻居打招呼都是要先新造一个机器人,然后派机器人去打招呼,接着招呼打完以后这机器人就被销毁了(机器人都沦为一次性产品,这得要多烧钱...),这就是这个机器人实例对象的“生存周期”。那么也就是说这个机器人类事实上是有很多个机器人实例对象的。
我这么一解释,大家都会觉得这种打招呼的方法成本实在是很高。如果是低成本的小机器人可能还可以接受,但是如果是我想派我的Girlfriend实例对象去打招呼就不能接受了!难道每次打完招呼我就得跟这个GF分手然后再找一个?你丫这简直就不是成本问题了!这个是道德问题!
在大型项目开发的时候,总会碰到一些在实例化对象的时候比较消耗资源的情况,比如反复读写同一个比较大的文件、或者反复链接数据库。这就好比它们是一个Girlfriend,每次要用到的时候再新造一个,用完再直接抛弃销毁是很不道德的事情!

那么这时候单例模式就派上用场了,那么它是怎么实现让一个类只能生成一个实例呢?,下面举一个最简单的单例模式的示例(非线程安全):
Girlfriend类

package com.iteye.bolide74.action;

public class Girlfriend {
private static Girlfriend girlfriend; //类的静态成员属性形式声明一个实例

public String name;
public double height;
public double weight;

private Girlfriend(String name, double height, double weight) {
this.name = name;
this.height = height;
this.weight = weight;
}

public static Girlfriend getGirlfriend() {
if (girlfriend == null)
girlfriend = new Girlfriend("GirlFriend", 1.64, 99.99);
return girlfriend;
}

public void Speak(String msg) {
System.out.println(msg + ",我是" + this.name + ",我是唯一的女朋友");
}
}

大家可以看到这个Girlfriend类,跟前面一篇的静态工厂模式非常像,它们的构造函数都是private私有的,都有一个静态的实例生成方法。作用也是相同的,就是限制生成实例的办法只有一种,就是通过getGirlfriend()方法来获取。

区别有两点:
1) 静态工厂生成的实例对象{Robot2 robot2;},都是在getRobot()这个生产实例的方法[color=red]内部[/color]声明的,也就是说这些引用对象都是局部变量。
而单例模式里生成的实例对象{private static Girlfriend girlfriend;},是在getGirlfriend()这个生产实例的方法的[color=red]外部[/color]声明,并且还加了[color=red]static静态修饰词[/color]的,这就代表了它这个是类的静态成员属性,[color=blue]它是唯一的并且是无需实例化就能使用的[/color](因为是static的),只不过这里又加了一个[color=red]private私有修饰词[/color],不能被外部直接访问而已。
2) Girlfriend类里的getGirlfriend()方法内部,加了一个判断,作用是如果girlfriend这个静态类属性,同时也是这个类的一个实例对象为null,那么就新建一个实例对象给它;如果不为null,也就是已经有实例了,那么就直接返回已经存在的那个实例对象。
[quote]关于为什么这个类成员属性被设置为static静态的,就是唯一并且不需要实例化这个问题,这个涉及到内存机制,建议google一下相关知识。如果还是没搞懂的话请留言回复,我看到的话就会再抽空补充一下这个相关知识,如果这个不搞懂的话想要弄懂为什么单例模式是单例是不太可能的。[/quote]
如果能搞懂以上部分,那么就能明白单例模式的实现原理了。它就是在获取某个类的实例对象的时候,限制它的获取方法必须为getInstance()方法,也就是我这里的getGirlfriend()。而getGirlfriend()这个方法呢,除了第一次调用是真正生成了一个新的实例以外,以后每一次调用其实都是返回了相同的一个实例对象。这就做到了一个类只能生成一个实例对象了。

用下面的一段实现类代码就可以测试一下效果:
package com.iteye.bolide74.tester;

import com.iteye.bolide74.action.Girlfriend;
import com.iteye.bolide74.action.Robot2;

public class Tester {
public static void main(String[] args) {
Robot2 robot1 = Robot2.getRobot(1);
Robot2 robot2 = Robot2.getRobot(1);
System.out.println(robot1 == robot2);
//输出结果为false,也就是说两者是不同实例对象

Girlfriend girlfriend1 = Girlfriend.getGirlfriend();
Girlfriend girlfriend2 = Girlfriend.getGirlfriend();
System.out.println(girlfriend1 == girlfriend2);
//输出结果为true,两个引用对象 引用的是同一个实例对象
girlfriend1.Speak("Hello,World!");
}
}

小知识:“==”逻辑运算和equals方法是不同的,"=="是判断引用对象(也可以说是C里面的指针)是否引用了同一个实例对象(也就是是否指向同一个内存片段);而equals方法只是判断两个引用对象引用的实例对象的值是否是相等的,而不考虑是否是同一个内存片段,典型的示例:
		String str1="123";
String str2=new String("123");
System.out.println(str1==str2); //false
System.out.println(str1.equals(str2)); //true



单例模式的基本原理我已经介绍完了,但是我要重点提醒一下,上面的Girlfriend类,并不是一个完善的单例模式,它只能起到一个介绍原理的作用。
在单线程项目里使用这个Girlfriend单例类不会有太大问题,但是一旦涉及到并发多线程那么就会出很严重的问题了。

举个例子(举个栗子LoL):我家除了我在以外,还有我爹妈也都在,他们也都是宅人+使唤癖(物以类聚)。假设我们只能指挥我们家房间以内的东西。这时候我的Girlfriend还在门外(说明已经有了引用对象了,知道可以使唤Girlfriend)但是还没进房间(就是这个引用对象的实例还是null)。这时候我跟我爸妈就异口同声的瞬间同时(就先假设真正意义上的同时吧..)让我Girlfriend进门,然后去跟不同的邻居打招呼。
在这种情况下,Girlfriend就会展现出超能力了,她会瞬间分裂出三个分身来听从我们三个人的命令,这就相当于原本只能是一个实例的,结果出来了三个不同的实例对象。
这是因为原先单线程的情况下,我们一家三口是不会同时叫Girlfriend进门的,比如我已经把她叫进门了,我爸妈再想叫她的时候都会先判断她是不是已经在房间里了,如果已经在了,那当然是不用叫直接就使唤呗。
但是在多线程情况下,三个人同时叫Girlfriend之前都是经过判断发现在那一瞬间Girlfriend确实是不在房间里的,所以理所当然的他们都会拖一个Girlfriend进门,结果Girlfriend就被迫影分身了。

要解决并发多线程下的安全的单例模式,就得再在getGirlfriend方法上加上同步锁,同时内部判断是否为null的时候还要加上双重判断等等方法才能实现线程安全的单例模式。这个要解释的话就得涉及到并发多线程了,这里就暂时不做解释。有兴趣的可以翻一下其他的文章。


另外由于java的反射机制,还有一种方法可以破解安全的单例模式,那就是直接把现有的实例序列化,然后再克隆一个序列化的实例,再通过Classloader之类的方法把它反序列化。大概原理似乎是这样,以前看到过相关文章但是没去仔细看过,就是知道有这么一个方法,有兴趣的话可以去google一下。
我提出这么一个事情,也就是提醒大家单例模式也不是真的能完全让一个实例唯一存在,总会有那么些突破的方法,所以一定要注意。


下一篇:Spring温故知新(四)用HashMap写一个自己的Spring IoC简易容器吧![url]http://bolide74.iteye.com/blog/1002610[/url]
上一篇:Spring温故知新(二) IoC控制反转与DI依赖注入[url]http://bolide74.iteye.com/blog/998650[/url]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值