本来今天打算介绍一下Spring的IoC容器,但是开了一早上的会感觉时间有点紧,今天写有点够呛。再加上看到昨天的访客大部分都对设计模式比较感兴趣,那么我就先提前介绍一下设计模式里也是比较重要的singleton单例模式。
单例模式之所以提前在这里介绍,是由于Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方。
那么什么是单例模式呢?我先引用一下别的地方抄来的理论介绍:
引用
单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
这个理论介绍其实还是比较通俗易懂的,这里沿用我前面一篇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()这个生产实例的方法
内部声明的,也就是说这些引用对象都是局部变量。
而单例模式里生成的实例对象{private static Girlfriend girlfriend;},是在getGirlfriend()这个生产实例的方法的
外部声明,并且还加了
static静态修饰词的,这就代表了它这个是类的静态成员属性,
它是唯一的并且是无需实例化就能使用的(因为是static的),只不过这里又加了一个
private私有修饰词,不能被外部直接访问而已。
2) Girlfriend类里的getGirlfriend()方法内部,加了一个判断,作用是如果girlfriend这个静态类属性,同时也是这个类的一个实例对象为null,那么就新建一个实例对象给它;如果不为null,也就是已经有实例了,那么就直接返回已经存在的那个实例对象。
引用
关于为什么这个类成员属性被设置为static静态的,就是唯一并且不需要实例化这个问题,这个涉及到内存机制,建议google一下相关知识。如果还是没搞懂的话请留言回复,我看到的话就会再抽空补充一下这个相关知识,如果这个不搞懂的话想要弄懂为什么单例模式是单例是不太可能的。
如果能搞懂以上部分,那么就能明白单例模式的实现原理了。它就是在获取某个类的实例对象的时候,限制它的获取方法必须为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简易容器吧!
http://bolide74.iteye.com/blog/1002610
上一篇:Spring温故知新(二) IoC控制反转与DI依赖注入
http://bolide74.iteye.com/blog/998650
分享到:
相关推荐
SpringSpringSpring温故知新六AOP向切面程Spring温故知新六AOP向切面程
NULL 博文链接:https://bolide74.iteye.com/blog/1050199
spring cloud + openshift example
常用的spring注解大全,适合新手学习、老手温故知新。读懂spring,平步青云。
spring cloud config
温故知新ASP.NET 2.0(C#)温故知新ASP.NET 2.0(C#)温故知新ASP.NET 2.0(C#)温故知新ASP.NET 2.0(C#)温故知新ASP.NET 2.0(C#)
“温故知新”系列之工业机器人行业复盘(三):从零部件国产化看产业链协同发展(附报告).pdf
机械军工行业:“温故知新”系列之工业机器人行业复盘(二):从美国汽车行业“2mm工程”看工业机器人国产化空间.pdf
计算机小数表示.温故知新.pdf
数学:吃透课本温故知新.docx
初中语文文学讨论现当代文学温故知新
20210202-银河证券-“温故知新”系列之工业机器人行业复盘(二):从美国汽车行业“2mm工程”看工业机器人国产化空间.pdf
中信建设温故知新,从 4G 看 5G.rar
中信建设温故知新,从 4G 看 5G.pdf
行业报告---温故知新,从 4G 看 5G
复习知识整理文档,其中介绍了spring的知识和注意点,还包含面试中常被提及的知识,帮助你快速回忆和复习知识,温故知新
通信行业:温故知新,从4G看5G-1202-中信建投-12页.pdf
固定收益专题报告:温故知新,国债期货1903合约回顾与总结_国信证券-13页.pdf
NULL 博文链接:https://macleo.iteye.com/blog/1045211
农林牧渔行业专题研究:温故知新,复盘上两轮生猪疫情影响-0213-广发证券-17页.pdf