`
bolide74
  • 浏览: 84179 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

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

阅读更多
本来今天打算介绍一下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









6
3
分享到:
评论
7 楼 sh305520891 2011-05-06  
你的单粒太粗糙了!
我给你随手写个吧(饿汉式)
public class SingleTon {
    private static SingleTon instance = new SingleTon();
    
    //  单粒模式构造
    private SingleTon() {}
    
     public sychronized static SingleTon getInstance() {
         return instance;
     }
}
6 楼 sh305520891 2011-05-06  
public class SingleTon {
   private static SingleTon instance = new SingleTon();

}
5 楼 C.T 2011-04-29  
lz这种是懒汉单列模式,当有需要的时候才被创建出来,还有另一种是饿汉单列模式,就是在一开始就创建对象,而不允许对象再次被创建,可以作为一个补充吧,当然还有线程安全的问题,线程安全的单列模式也有几种的实现方式。
4 楼 bolide74 2011-04-24  
ericslegend 写道
关于单例的各种情况处理,楼主可以看这个http://www.iteye.com/topic/575052

嗯,这篇博文是一个很好的并发情况下的单例模式的比较详细补充,大家有需要的话也都可以看一下
3 楼 ericslegend 2011-04-23  
关于单例的各种情况处理,楼主可以看这个http://www.iteye.com/topic/575052
2 楼 bolide74 2011-04-13  
javaeye 写道
1) 静态工厂生成的实例对象{Robot2 robot2;},都
这句话好像有些问题。按我的理解,对象是保存在堆空间,是全局性的,只要获得了这个对象的引用,就可以访问该对象。
如果说robot2是局部变量,那也就无法返回。

嗯,是我没说清楚产生的误解。事实上robot2这个是局部的引用对象(指针变量),它的生存周期只存在于这个方法以内,方法调用完以后,robot2就被销毁了。但是由于robot2销毁之前被return到调用这个方法的引用对象上了(可能是复制了一个引用对象),所以robot2所引用的值,也就是new出来的那个实例对象并没有由于没被任何对象引用就被销毁了,因为它被外部调用这个方法的引用对象给引用了。
简单的说就是robot2作为一个局部的指针变量已经被销毁了,但是由于它销毁前被return复制了一份给了外部的指针,所以robot2指针指向的那块内存,也就是new Robot2()这个实例对象还是没被回收,依然存在

不知道这么解释是不是对的?
1 楼 jupiterful 2011-04-13  
javaeye 写道
1) 静态工厂生成的实例对象{Robot2 robot2;},都是在getRobot()这个生产实例的方法内部声明的,也就是说这些实例对象都是局部变量。


这句话好像有些问题。按我的理解,对象是保存在堆空间,是全局性的,只要获得了这个对象的引用,就可以访问该对象。
如果说robot2是局部变量,那也就无法返回。

楼主写的不错,比喻很生动,很有启发。

相关推荐

    Spring温故知新六AOP向切面程

    【Spring AOP 向切面编程详解】 Spring框架的核心特性之一就是AOP(Aspect Oriented Programming,面向切面编程)。AOP提供了一种模块化和声明式的方式来处理系统中的横切关注点,如日志、事务管理、权限检查等。在...

    图解Java设计模式.zip

    1. **创建型设计模式**:这类模式主要关注对象的创建过程,包括单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式。例如,单例模式确保一个类只有一个实例,并提供全局访问点;工厂模式则提供了一种创建...

    spring_2021.7.14.zip

    描述中提到是"项目源码笔记",适合"入门级别或者复习用",意味着这份资料可能是作者在学习或实践中逐步积累的,适用于初学者巩固基础知识或者有经验者温故知新。所有内容均为作者亲手编写,保证了资料的原创性和可靠...

    Spring全家桶思维导图

    对于经验丰富的开发者,它则可以帮助温故知新,快速回忆起Spring的各项技术。 首先,Spring的核心组件包括Spring Core和Spring Beans,它们提供了依赖注入(DI)和面向切面编程(AOP)的基础。依赖注入使得对象之间...

    Head.First.Design.Patterns.2004

    - **单例模式(Singleton)**:确保一个类只有一个实例,并提供全局访问点。 - **工厂方法模式(Factory Method)**:定义一个用于创建对象的接口,让子类决定实例化哪一个类。 - **抽象工厂模式(Abstract ...

    spring cloud + openshift

    spring cloud + openshift example

    springcloud视频学习6-2

    在描述中提到的学习资源被高度推荐,这意味着该视频教程可能对初学者极其友好,同时也适合有一定经验的开发人员温故知新。通过观看这样的视频,学习者可以深入了解SpringCloud的核心组件及其用法,提升微服务开发...

    Spring注解大全,常用,随时查看,学习

    常用的spring注解大全,适合新手学习、老手温故知新。读懂spring,平步青云。

    spring cloud config

    Spring Cloud Config 是一个用于分布式系统配置管理的框架,它允许开发者在远程服务器上集中管理和版本化应用的配置,而不是在每个应用本地存储配置。这种方式在微服务架构中尤其有用,因为多个独立的服务需要共享和...

    Head.First.设计模式中文版pdf(第二部分/共七部分)

    《Head First 设计模式》是本深受程序员欢迎的图书,它由一群在编程教育领域有丰富经验的专家所著,其中包括了Eric Freeman、Elizabeth Freeman夫妇,Kathy Sierra以及Bert Bates。四位作者不仅将他们各自的编程背景...

    “温故知新”系列之工业机器人行业复盘(三):从零部件国产化看产业链协同发展(附报告).pdf

    “温故知新”系列之工业机器人行业复盘(三):从零部件国产化看产业链协同发展(附报告).pdf

    thinkjava第三版

    20. 设计模式:书中可能涵盖单例模式、工厂模式、观察者模式等设计模式,提升代码的可读性和可维护性。 《ThinkJava第三版》涵盖了Java编程的各个方面,旨在帮助读者从零开始掌握Java语言,通过丰富的实例和详尽的...

    “导学导练强记多练”模式在高三政治复习中的应用探索.doc

    “导学导练 强记多练”模式源于对各地成功教学模式的借鉴,如杜郎口中学的“三三六”模式、洋思中学的“先学后教,当堂训练”以及山东昌乐二中的“271 高效课堂模式”。这些模式强调学生自学能力的培养和学习效率的...

    jsp,servlet,filter温故知新

    本文将深入探讨这些技术,帮助开发者温故知新,理解它们的功能、工作原理以及如何在实际项目中应用。 ### JSP(JavaServer Pages) JSP是一种动态网页技术,它允许开发者在HTML页面中嵌入Java代码,以实现动态内容...

    初中语文文学讨论现当代文学温故知新

    初中语文文学讨论现当代文学温故知新

    ToyCode:设计模式,数据结构,算法等的简单代码

    单例模式确保一个类只有一个实例,工厂模式则提供了创建对象的最佳方式,而观察者模式允许对象间建立一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。通过这些代码...

    中信建设温故知新,从 4G 看 5G.pdf

    中信建设温故知新,从 4G 看 5G.pdf

    java基础知识30个经典问答

    常见的设计模式有工厂模式、单例模式、装饰器模式、观察者模式等,它们是解决特定编程问题的通用策略。 16. **JDBC是什么?** JDBC是Java数据库连接的缩写,是Java访问数据库的标准API。 17. **Spring框架的核心...

    《三角形分类》.doc

    - 难点在于深入体会每种三角形的独特性质,例如直角三角形有一个90度角,等腰三角形两边长度相等,等边三角形三边长度都相等。 3. **方法指导**: - 引导法:教师通过引导让学生主动思考如何对三角形进行分类。 ...

Global site tag (gtag.js) - Google Analytics