本来今天打算介绍一下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
分享到:
相关推荐
【Spring AOP 向切面编程详解】 Spring框架的核心特性之一就是AOP(Aspect Oriented Programming,面向切面编程)。AOP提供了一种模块化和声明式的方式来处理系统中的横切关注点,如日志、事务管理、权限检查等。在...
1. **创建型设计模式**:这类模式主要关注对象的创建过程,包括单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式。例如,单例模式确保一个类只有一个实例,并提供全局访问点;工厂模式则提供了一种创建...
描述中提到是"项目源码笔记",适合"入门级别或者复习用",意味着这份资料可能是作者在学习或实践中逐步积累的,适用于初学者巩固基础知识或者有经验者温故知新。所有内容均为作者亲手编写,保证了资料的原创性和可靠...
对于经验丰富的开发者,它则可以帮助温故知新,快速回忆起Spring的各项技术。 首先,Spring的核心组件包括Spring Core和Spring Beans,它们提供了依赖注入(DI)和面向切面编程(AOP)的基础。依赖注入使得对象之间...
- **单例模式(Singleton)**:确保一个类只有一个实例,并提供全局访问点。 - **工厂方法模式(Factory Method)**:定义一个用于创建对象的接口,让子类决定实例化哪一个类。 - **抽象工厂模式(Abstract ...
spring cloud + openshift example
在描述中提到的学习资源被高度推荐,这意味着该视频教程可能对初学者极其友好,同时也适合有一定经验的开发人员温故知新。通过观看这样的视频,学习者可以深入了解SpringCloud的核心组件及其用法,提升微服务开发...
常用的spring注解大全,适合新手学习、老手温故知新。读懂spring,平步青云。
Spring Cloud Config 是一个用于分布式系统配置管理的框架,它允许开发者在远程服务器上集中管理和版本化应用的配置,而不是在每个应用本地存储配置。这种方式在微服务架构中尤其有用,因为多个独立的服务需要共享和...
《Head First 设计模式》是本深受程序员欢迎的图书,它由一群在编程教育领域有丰富经验的专家所著,其中包括了Eric Freeman、Elizabeth Freeman夫妇,Kathy Sierra以及Bert Bates。四位作者不仅将他们各自的编程背景...
“温故知新”系列之工业机器人行业复盘(三):从零部件国产化看产业链协同发展(附报告).pdf
20. 设计模式:书中可能涵盖单例模式、工厂模式、观察者模式等设计模式,提升代码的可读性和可维护性。 《ThinkJava第三版》涵盖了Java编程的各个方面,旨在帮助读者从零开始掌握Java语言,通过丰富的实例和详尽的...
“导学导练 强记多练”模式源于对各地成功教学模式的借鉴,如杜郎口中学的“三三六”模式、洋思中学的“先学后教,当堂训练”以及山东昌乐二中的“271 高效课堂模式”。这些模式强调学生自学能力的培养和学习效率的...
本文将深入探讨这些技术,帮助开发者温故知新,理解它们的功能、工作原理以及如何在实际项目中应用。 ### JSP(JavaServer Pages) JSP是一种动态网页技术,它允许开发者在HTML页面中嵌入Java代码,以实现动态内容...
初中语文文学讨论现当代文学温故知新
单例模式确保一个类只有一个实例,工厂模式则提供了创建对象的最佳方式,而观察者模式允许对象间建立一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。通过这些代码...
中信建设温故知新,从 4G 看 5G.pdf
常见的设计模式有工厂模式、单例模式、装饰器模式、观察者模式等,它们是解决特定编程问题的通用策略。 16. **JDBC是什么?** JDBC是Java数据库连接的缩写,是Java访问数据库的标准API。 17. **Spring框架的核心...
- 难点在于深入体会每种三角形的独特性质,例如直角三角形有一个90度角,等腰三角形两边长度相等,等边三角形三边长度都相等。 3. **方法指导**: - 引导法:教师通过引导让学生主动思考如何对三角形进行分类。 ...