关于作者

用户名:crazybest
笔名:crazybest
地区: -
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言


linux

访问统计:
文章个数:82
评论个数:6
留言条数:0




Powered by BlogDriver 2.1

crazybest的博客

 

欢迎访问crazybest的博客

文章

1
1 1111111111111111111111111111111111111111111

- 作者: crazybest 2007年06月27日, 星期三 00:37  回复(0) |  引用(0) 加入博采

帮你免于失业的十大软件技术

能跟上关键技术的发展,是你在就业市场和未来保持优势的最佳手段。你对我们

列出的十门技术精通吗?哪怕是大略精通?

我不知道你的具体工作是什么。如果你是某冷门领域的专家而变得不可或缺,或

者你们的公司十分稳固,以你现有的技术就足以安度你的职业生涯,那我不知道

你是否有失业的危险。不管怎样,请你先不要急着寄出那些告诉我你们的公司对

用VB3十分满意或你们除C以外永不用其它语言的信件,并保留起那些讲述.NET将

如何把Java扫地出门、XML将如何取代关系型数据库、或你怎样能用汇编语言或C

写任何软件而任何其它开发工具都不重要的信件。

现在,想象一下如果你像很多受裁员和公司倒闭影响的同行一样失去了工作,那

将会怎样。如果你突然需要搬往它处该怎么办?如果你的公司突然做了技术转向

又怎么办?

在目前的就业市场,经理们是根据当前和未来的开发需要招聘雇员。作为开发人

员,你所掌握的知识是你找工作和保住工作的关键。你要了解技术的发展方向,

跟上技术的变化,这一点十分重要,即使你永远不用再找开发方面的工作。

这里是我选出的当前最重要的十大开发技术,最重要的排在前面。看完本文后,

请务必加入talk.editors.devx讨论组,提出你选出的十大技术。

1. XML

首先,你要了解XML。我不是说仅仅是XML规格本身,还包括一系列相关的基于XML

的语言:最重要的是XHTML、XSLT、XSL、DTDs、XML Schema (XSD)、XPath、

XQuery和SOAP。那些在过去5年内从未碰过键盘的人,可能不知道XML为何物。XML

是一种文本文件,使用与HTML类似的标记。XML能定义一个树状结构,并能描述所

含的数据。

XML最好的一点是既能存结构化数据也能存非结构化数据。它既能存贮和描述“规

格的”(regular)表格数据,也能容纳和描述“粗糙的”(ragged)文件数据。

XHTML是现今写HTML的首选方法。因为它是形式完好(well formed)的XML,比起

古老的、通常是畸形(malformed)的HTML文件,XHTML格式的文件更容易处理。

XSLT和XSL是用于把XML文件转成其它格式的语言。可转换的格式包括:文本文件

、PDF文件、HTML、以逗号为分隔符的文件,或其它XML文件。

DTD和XML Schema描述XML文件所能包含的内容的类型,并让你“验证”XML文件内

容的合理性,而不用写特殊代码以确保内容符合规则要求。

XPath和XQuery是用于从XML文件中抽取单个项目或一组项目的查询语言。XQuery

扩展了XPath,因而更重要。XQuery与XML的关系正像SQL与关系数据库的关系。

SOAP是Web服务之间的一个标准通讯协议。尽管你不需要对SOAP标准一清二楚,你

应该熟悉一般的schema和它的工作原理,以便能应用这门技术。

2. Web服务

Web服务是XML流行后的一个直接产物。因为你能用XML描述数据和物件,因为你能

用schema确保XML文件内容的合理性,因为XML是基于文本的规范,XML为跨平台通

讯标准提供了一个极其方便的基本格式。如果你还从来没碰到Web服务,你可能很

快就会碰到,在未来5年内,你几乎肯定会碰到。熟悉Web服务十分重要,因为它

是目前所有跨不同机器、不同语言、不同平台和不同地点的通讯协议中最简单的

一个。不管你需要与否,Web服务是迈向互用性的重要一步。

XML工作组主席John Bosak曾说XML“给Java一些事做”。实际上,Web服务让所有

语言都有了一些事做。Web服务让在大型机上运行的COBOL应用软件能调用在手持

设备上运行的Java应用程序、能让Java applet与.NET服务器交谈、能让微机软件

与Web服务器无缝连接,并提供了一个相对容易的方法,让企业不光能向外界提供

数据,还能提供功能,而且是一种与语言、平台和位置都独立的方法。

3. 面向对象的编程

很多程序员仍认为OOP是象牙塔里的技术。但如果你想一下是什么语言在过去的10

年里占主导地位,你就会理解OOP不是象牙塔里的技术。OOP从Smalltalk开始,传

到C++和Pascal (Delphi)。Java使OOP大踏步地迈向主流,几年后的VB.NET和C#则

完全确立了OOP的优势地位。尽管这些语言中的多数并不要求你必须会OOP,但我

觉得如果你不了解OOP的基本概念也不知道如何应用这些概念,你能找到的编程工

作将越来越少。

4. Java、C++、C#和VB.NET

我把这些语言列在一起,并不是建议你成为每一种语言的专家。我的理由是:学

习编程最有效的方法之一是看代码,而你能看到的大量的代码很可能不是用你所

喜爱的语言编写的。

在过去几年,各语言的能力越来越接近。现在,你可以用VB.NET写Windows服务、

Web应用程序或命令行程序。即使你只使用一种语言,你也应该学一些其它语言,

以便能看懂那些样例,并将其翻译到你所用的语言。这4种语言是基本核心,还有

其它一些满足不同需要、颇具用途的语言,如FORTRAN、COBOL、APL、ADA、Perl

和Lisp。

5. JavaScript

尽管名字有些相像,但Java与JavaScript并无关联。为什么一个脚本语言会如此

重要呢?因为所有主流浏览器都用JavaScript。如果你需要写Web应用程序,你就

有足够的理由学JavaScript。JavaScript可以用作ASP或ASP.NET的服务器语言,

也可以当做用于扩展XSLT的功能语言(functional language)。JavaScript是

Mozilla/Netscape中用于激活基于XUL的程序接口的首选语言。JavaScript的一个

变种ActionScript是Flash MX的编程语言。将来,JavaScript很可能成为新设备

的编程语言,以及大型应用软件中的宏语言。

与JavaScript相对照的是VBScript。尽管Microsoft的软件对VBScript有良好的支

持,但VBScript在未来的开发工作中很可能是一个糟糕的选择。就是Microsoft也

倾向于用JavaScript(或Microsoft自己的变种:JScript)写客户端程序。在选

择脚本语言时,请选择JavaScript。

6. 正则表达式(Regular Expressions)

查寻关系数据库可以用SQL,查询XML可以用XPath和XQuery,查询纯文本文件则可

以用正则表达式。例如,你可以用一个命令从一个HTML文件中查找并删除所有的

注释。各种开发语言内置的一些简单的文本查询功能,如"IndexOf"函数或VB中经

典的"InStr"函数或"Like"操作符,根本不能与正则表达式相提并论。现在,各种

主要的开发语言都提供使用正则表达式的途径。尽管正则表达式本身既难懂更难

读(是回到早期计算机时代的一种倒退),但它却是一个功能强大而且未被充分

利用的工具。

7. 设计模式

正像OOP通过把对象分类以简化编程一样,设计模式对一些普遍的对象之间的交互

进行分类,并赋予一个恰当的名称。OOP用得越多,设计模式就越有用。一些最常

用的模式的名称已经变成了软件开发领域共同使用的术语,所以要跟上信息的主

流,你就要对设计模式有相当的理解。

8. Flash MX

如果你需要在客户端得到比HTML和CSS更多的图形和更强的编程功能,Flash是你

的答案。用Flash编程比开发Java applets或写.NET代码要快得多,也容易得多。

在最新版本 (MX) 中,Flash不仅仅是画图和制造动画的工具,它已经成为一个编

程功能强大的开发环境:能调用SOAP Web服务,也能调用远端服务器上的

ColdFusion、Java或.NET程序。Flash无处不在。它的引擎存在于世界上大多数客

户端计算机,包括手持设备、置顶盒、甚至是新的书写板电脑。所以使用Flash能

大大扩展你的程序的应用范围。

9. Linux/Windows

熟悉Linux。在一台旧机器或新机器上安装Linux。下载图形用户界面,在其基础

上写一些程序。安装Apache,写一个Web应用程序。这个世界不再仅仅是属于

Windows,这种趋势可能还会持续下去。如果你是一名中坚的Linux开发人员,那

就抛弃你对Windows的憎恶,看看你能否做一些Windows编程。Windows能继续在台

式电脑上称王是有其原因的,这不仅仅是因为Microsoft控制了这个市场。

没人知道你们公司会在什么时候决定从Linux转向Windows(或从Windows转向

Linux),或者你想跳到一家用另一种平台的公司,或者你想出了开发一个杀手软

件的好主意,所以你要争取拥有在不同操作系统上的编程经验。

10. SQL

尽管SQL不像本文讨论的其它技术那样新,而且SQL的重要性在未来10年内很可能

降低,但它仍然是一项基本技能。很多开发人员还没有掌握这门技术,或掌握得

不够,不足以有效率地使用它。不要依赖具有图形用户界面的SQL生成器替你做事

情,你要自己手工地写查询命令,直到你熟悉基本的SQL语法为止。了解SQL不仅

能帮助你日后学习XQuery,你还有可能马上发现能简化或改进目前项目的方法。

培养好奇心

最后,(对,我意识到这是第11门技术),好奇心是你最重要的技能。要去尝试

各种东西。新语言或新技术对你当前或将来的工作可能有用,也可能没用,但并

不是你所学的每一件事都是为了工作。不要害怕失败,万事开头难,学新技术也

是如此。大多数失败是因为人们希望太快地学到太多的东西。要对每一点进步感

到满意,不要让时间(或缺乏时间)妨碍你。相反,你要安排时间留心、研究、

试验新的开发技术和工具。

你可能永远也没有必要成为这些技术的专家,而且我的选择可能根本不适合你的

特殊情况,但通过培养好奇心,你将会发现你应该了解的东西。

- 作者: crazybest 2007年03月19日, 星期一 00:54  回复(0) |  引用(0) 加入博采

Java十年 十大产品

Sun JDKJava的基石                                                                                              

众所周知,流传于市的JDK不单Sun一家,比如IBMJDKBEAJRocketGNUGCJ,以及如Kaffe这样的开源实现,不一而足。但是,根正苗红的Sun官方JDK一直以来都是备受瞩目的主流,它对Java社区的影响也是举足轻重。19961月,Sun在成立了JavaSoft部门之后,推出了JDK 1.0,这是Sun JDKJava Development Kit)的首个正式版本;当年12月,JDK1.1出炉。该版除了对前序版本部分特性做了改进以外,重写了AWT,采用了新的事件模型。199812 月,JDK 1.2正式发布。此时的类库日臻完善,API已从当初的200个类发展到了1600个类。在1.2版本中引入了用100%Java代码写就的 Swing,同时,SunJava更名为Java 21999年,Java 技术形成了J2SEJ2EEJ2ME三大格局。Sun向世人公布了Java HotSpot性能引擎技术的研究成果。HotSpot旨在进一步改善JVM性能,提高Java ByteCode的产生品质,加快Java应用程序的执行速度。J2SE 1.3发布于2000年;20022月间,J2SE 1.4问世,这是有JCP参与以来首个J2SE的发行版本。2004930,代号为“Tiger”的J2SE 5.0终于出笼了,这次发布被誉为Java平台历来发布中特性变动最大的一次。包括泛型在内的若干重大语法改进、元数据支持,包括多线程、JDBC在内的多项类库改进,都令广大Java程序员激动不已。自此,Sun的官方JDKJ2SE Development Kit)已经步入了一个新的高度。

 

Eclipse:以架构赢天下                                                                                             

IBM是在2001年以4000万美元种子基金成立Eclipse联盟,并且捐赠了不少程序代码。如今,该组织有91个会员,包含许多全球最大的软件商。根据Evans Data公司的资料,Eclipse是目前最受欢迎的Java开发工具。Java厂商若要共同对抗微软,彼此之间就要有共同的开发工具才行。在Eclipse平台上,程序员可使用好几种不同的语言。在前端方面,用户可整合多种工具来撰写Plug-in程序或Unit TestEclipse最大的特色就在于其完全开放的体系结构,这代表任何人都可下载并修改程序代码,给Eclipse写插件,让它做任何你能想到的事情,即所谓“Design for everything but nothing in particular”。Eclipse基金会的架构比较特别,反映出企业现今对于开放原始码计划也越来越积极主动。Eclipse不像一般开放源码软件容许个人的捐献程序,该基金会是由厂商主导。不论是董事会成员或者是程序赞助者几乎都来自于独立软件开发商(ISVs)的员工。Eclipse首席执行官Mike Milinkovich说,这种厂商会员制是特意设计的;他说Eclispe软件开发快速就是因为会员制的关系,同时又加上开放源码开发模式的临门一脚。这与一般透过标准组织的做法全然不同。这其实正好验证了一句老话:“开放即标准”。

 

JUnit/Ant:让Java自动化的绝代双骄                                                                   

Java程序员必备的工具中,共同拥有且交口称赞的恐怕就非JUnitAnt莫属了。一个是单元测试的神兵利器,一个是编译部署的不二之选,它们让Java的开发更简单。JUnitXPTDD的创始人、软件大师Kent Back以及Eclipse架构师之一、设计模式之父Erich Gamma共同打造。名家的手笔和理念使得JUnit简单而强大,它将Java程序员代入了测试驱动开发的时代。JUnit连任了20012002 Java World编辑选择奖”以及2003年“Java World最佳测试工具”和2003年“Java Pro最佳Java测试工具”等众多奖项,深受Java程序员好评。Ant是开源项目的典范,它让IDE的功能更加强大,从SunNetBeansJBuilder,主流的IDE中处处都有它的身影。“Another Neat Tool”原是它的本名,但这已经渐渐不为人知。它彻底地让部署自动化,而程序员需要做的仅仅是几条简单的配置命令。和JUnit一样,Ant也荣获了众多的殊荣:2003JavaWorld“最有用的Java社区开发的技术编辑选择奖”、 2003Java Pro“最有价值的Java部署技术读者选择奖”,2003年“JDJ编辑选择奖”,也让Ant受到的多方的认可。AntJUnit的全面集成,则使得一切都变得更加完美。只需简单地配置,从自动测试到报告生成,从编译到打包部署均可自动完成。强大的功能,简单的配置,让Java程序员高枕无忧。实可谓让Java自动化的绝代双骄。

 

Websphere:活吞市场的大鲸                                                                                     

1999年,IBMNovell签订合作协议,成功地提供电子商务的解决方案给予原先使用NetWare的用户。同年更是推出了WebSphere Application Server 3.0,并且推出WebSphere StudioVisualAge for Java让工程师可以快速开发相关的程序。2001年,IBM更是宣布将应用服务器、开发工具整合在一起,与DB2 TivoliLotus结合成为一套共通解决方案,如今、IBM更是并入了Rational Rose ( UML tools )让开发流程更是完整化。SunWeb Services的策略方面远远落后于微软与IBM,当他们手拉手在研订Web Services规范,加上IBM买硬件送软件或是买WebSphereDB2的策略让企业大佬们纷纷转向IBM的阵营,Sun才惊觉大势已去。WebSphere复杂的安装,深奥的设定,难以理解的出错讯息不断地挑战开发者的耐心与毅力。IBM如今已经不是将WebSphere定义为单一产品,它已经是一个平台的代名词。它里面的产品目前包含了应用服务器、商业整合、电子商务、数据讯息管理、网络串流、软件开发流程、系统管理、无线语音等等。非常多样化,也让企业界愿意相信WebSphere可以带给他们一套完整的解决方案。同时,IBM也在推广SOA的概念,简单来说,利用Web Service的耦合性与工作流程的整合,为企业内部打造以服务为导向的架构。IBM捐献出Eclipse带给Java开发人员对IDE的重新掌握。未来是否会捐献出WebSphere的哪一个部分成为OpenSources,或许,又是改写Java世界的时刻了。

 

WebLogic:技术人的最爱                                                                                         

1995年,BEA成立了,初期以Tuxedo数据转换的产品为基础,成长之迅速是历年来最强的企业。1998年,BEA推出以Java为基础的网络解决方案,提供了完整的中间层架构,更同时支持EJB 1.0及微软的COM组件,方便的管理接口掳掠了工程师的心。在IBMOracle尚未准备好迎击的时候,BEA已经席卷企业应用平台的市场。WebLogic无论在市场领先度与技术领导性与策略远观性都优于当年的所有应用服务器厂商。如今WebLogic不仅仅是应用平台服务器的名称,而是BEA对于整个企业解决方案的总称,无论是WebLogic Portal或是WebLogic Integration配合着Workshop开发环境,来自微软的UI开发团队让Workshop几乎达到所见即所得。接着,在下一个版本之中,BEABeeHive开放源代码计划将释出中间层控件的开发模块,并且与Eclipse合作共同打造新一代的开发环境。如此强而有力的技术支持,更是让顾客愿意使用WebLogic平台的最大原因。代号为“Diablo”的 WebLogic Server 9.0小恶魔已经出现了,目前虽然仅仅是BETA版,以Portlet方式打造的管理接口与完整且美妙的WebServices支持,实在很难找到可以挑剔的地方,虽然去年被IBM的技术性推销超越了市场占有率,不过接下来SOA的平台竞争现在才开始,BEALOGO也加入“Think liquid”并且推出新的AquaLogic平台做为数据服务平台,可见,Java的应用服务器的战争,还会继续进行着。

 

JBuilderJava开发工具的王者                                                                            

Java的开发工具中,最出名的莫过于Borland公司的JBuilder了。对于一些没有弄清楚开发工具与JDK的区别的Java入门者来说,JBuilder就如同Visual C++之于C++,以为JBuilder就是Java的全部。比起捆绑在服务器上销售的JDeveloperJBuilder应该是唯一的仅靠自身的实力而占领了大部分市场的Java商用开发工具了。而JBuilder作为Java 开发工具的王者,其夺冠之路并非一帆风顺。直到Java的天才Blake Stone成为JBuilderArchitect之后,JBuilder 2.0以及3.0才逐渐推出。2000314JBuilder 3.5的推出别具意义,它成为了业界第一个用纯Java打造的开发工具,也风靡了整个Java开发工具市场。在同年11月份推出的JBuilder 4.0乘胜追击,冲破了50%的市场占有率,成为了真正Java开发工具的王者。Borland以每半年左右推出一个新版本的速度,让众多的对手倒在了沙场。而Microsoft因为与Sun的官司,也使得一个强大的对手退出了战争。2001年,加入了对企业协作支持的JBuilder 5以及强化了团队开发工具的JBuilder 6打败了最后一个对手Visual Age For Java2002JBuilder 7推出之后,再也没有其他厂商与JBuilder竞争。孤独的王者并没有停下脚步,在2003年到2005年间,JBuilder也仍然延续了其半年一个版本的速度,推出了89102005四个版本。强大的功能以及持续的改进,也让Java程序员多了一分对能够在开发工具市场上与Microsoft血拼十数年的Borland的敬仰。

 

OracleJava人永远的情结                                                                                      

在林林总总的数据库之中,有一种尤其令人又爱又恨、印象深刻,那就是关系型数据库市场的“大佬”――Oracle。从公司的角度,OracleSun有着诸多相似之处,例如:两家公司都拥有一位个性鲜明的CEO。早在Java诞生之初的1995年,Oracle就紧随NetScape从而第二个获得了Java许可证。从那以后,OracleJava的鼎力支持是Java能够在企业应用领域大获成功的重要原因之一。所有J2EE程序员都知道,OracleJDBC驱动虽然与Oracle数据库配合良好,但在不少地方使用了专有特性。其中最为著名的就是 CLOB/BLOB问题”,诸如此类的问题给开发者带来了很多麻烦。为了同时兼顾不同的数据库,他们不得不经常把自己的一个DAO(数据访问对象)写成两份版本:针对Oracle的版本和针对其他数据库的版本。有不少人为了开发便利,舍弃了数据库之间的可移植性,将自己的产品绑定在Oracle的专有特性上。Oracle提供的Java开发工具也与此大同小异。不管是数据库内置的Java支持还是JDeveloper IDEOracleJava工具都和Oracle数据库有着千丝万缕的联系。看起来,只要Oracle还是数据库市场上的“头牌”,了解、学习Oracle的专有特性,周旋于Oracle特有的问题和解决方案之中,就将仍旧是J2EE程序员在数据库基础和SQL之外的必修功课。对Oracle的爱与恨,也将仍旧是Java人心头一个难解的情结。

 

StrutsHibernate:让官方框架相形失色的产品                                                    

好的框架能够让项目的开发和维护更加便捷和顺利。相比Sun官方标准的迟钝以及固执,开源框架也更得到Java程序员的共鸣。Struts以及 Hibernate就是这样一类产品,它们简单、优雅,更让官方的产品相形失色。谈起Struts,不可避免地就要提及MVCModel-View-Controller)的理念。而准确地讲,MVC的提出却最早源于JSP的标准。在1998107号,Sun发布的JSP0.92的规范中提出的Model 2就是MVC的原型。在199912Java World的大会中,Gavind Seshadri的文章最早阐述了Model 2就是一种MVC的架构,同时也提及了MVC架构是一种最好的开发方法。20003月,由Craig McClanahan发布的Struts成为了最早支持MVC的框架。Struts在设计上虽然存在一些诟病,但是不可否认的是,它使得Java Web应用的开发更加简洁和清晰,也让更多的程序员爱上了Java,并开始遗忘官方的JSP。时至今日,比起如WebWorkTapestry以及 Sun官方的JSFStruts或多或少存在些不足,但是众多成功项目的实施,仍然使其牢牢占据的Java Web应用框架的首位。Hibernate则在某种程度上改变了人们对构建J2EE的思路。相比其EJBEntity Bean的映射技术,Hibernate则显得更加简洁和强大。五分钟就能把Hibernate跑起来,让更多的Java程序员享受到了开发的乐趣。第 15Jolt大奖中,最优秀数据库、框架以及组件的奖项中,Hibernate当仁不让获得头筹;不仅如此,Hibernate甚至还影响了官方的标准。在众多Java程序员翘首以待的EJB 3.0的规范中,Hibernate得到了支持。Java开源的繁荣不仅让众多Java的开发者享受到了更多的便利,甚至影响了官方的标准。恐怕这也是作为Java人独有的乐趣之一吧。

 

PetStoreJ2EE人的必修课                                                                                      

很少有一个例子项目如PetStore这般广为人知,而这很大程度上要归功于Sun很“英明”地把PetStore做成一个只展示架构而在性能调优上留下了大大余地的例子。围绕着性能话题,产生了颇为有趣的厂商之间以及平台之间的Pet Wars。除去这些关于性能的流言蜚语乃至中伤,PetStore在展示J2EE1.3平台的架构、演示什么叫分层方面还是有着很大的功劳的。而且 PetStore在架构方面的丰富性使得其成为J2EE的那些轻量级小兄弟们展示自身的一个必选科目。不谈那些围绕PetStore的口水,那些数不尽的盗版,PetStore给开发新手带来的最重大的影响,我想应该是架构的观念而不是性能,也不是业务。做为一种技术的Demo,这无可非议。但是如果你是一个新手,跟着PetStore亦步亦趋地学习J2EE开发,难免会陷入过度设计、华而不实之类的困境。围绕着.NETPetStore的克隆PetShop展开的架构与性能的大讨论,是不是也在促使我们学习新技术时应该以解决问题为导向呢?特别是当你想把一个如PetStore这般的Sample Project的技术照搬到你的现实世界的Real Project来时。

- 作者: crazybest 2007年03月19日, 星期一 00:52  回复(0) |  引用(0) 加入博采

深入浅出单例模式

一、引子

单例模式是设计模式中使用很频繁的一种模式,在各种开源框架、应用系统中多有应用,在我前面的几篇文章中也结合其它模式使用到了单例模式。这里我们就单例模式进行系统的学习。并对有人提出的单例模式是邪恶的这个观点进行了一定的分析。

 

二、定义与结构

单例模式又叫做单态模式或者单件模式。在GOF书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式中的单例通常用来代表那些本质上具有唯一性的系统组件(或者叫做资源)。比如文件系统、资源管理器等等。

单例模式的目的就是要控制特定的类只产生一个对象,当然也允许在一定情况下灵活的改变对象的个数。那么怎么来实现单例模式呢?一个类的对象的产生是由类构造函数来完成的,如果想限制对象的产生,就要将构造函数变为私有的(至少是受保护的),使得外面的类不能通过引用来产生对象;同时为了保证类的可用性,就必须提供一个自己的对象以及访问这个对象的静态方法。

现在对单例模式有了大概的了解了吧,其实单例模式在实现上是非常简单的——只有一个角色,而客户则通过调用类方法来得到类的对象。

放上一个类图吧,这样更直观一些:

 

单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象,多个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是不变单例对象,仅用做提供工具函数。

 

三、实现

在单例模式的实现上有几种不同的方式,我在这里将一一讲解。先来看一种方式,它在《java与模式》中被称为饿汉式。

 

public class Singleton {

//在自己内部定义自己一个实例

//注意这是private 只供内部调用

private static Singleton instance = new Singleton();

//如上面所述,将构造函数设置为私有

private Singleton(){

} 

//静态工厂方法,提供了一个供外部访问得到对象的静态方法  
  public static Singleton getInstance() {
    return instance;   
  }
}

 

       下面这种方式被称为懒汉式:P

 

public class Singleton {

       //和上面有什么不同?

private static Singleton instance = null;

//设置为私有的构造函数

private Singleton(){

} 

//静态工厂方法

public static synchronized Singleton getInstance() {

//这个方法比上面有所改进
         if (instance==null)

              instancenew Singleton();
         return instance;   

}

}

 

先让我们来比较一下这两种实现方式。

首先他们的构造函数都是私有的,彻底断开了使用构造函数来得到类的实例的通道,但是这样也使得类失去了多态性(大概这就是为什么有人将这种模式称作单态模式)。 

在第二种方式中,对静态工厂方法进行了同步处理,原因很明显——为了防止多线程环境中产生多个实例;而在第一种方式中则不存在这种情况。

       在第二种方式中将类对自己的实例化延迟到第一次被引用的时候。而在第一种方式中则是在类被加载的时候实例化,这样多次加载会照成多次实例化。但是第二种方式由于使用了同步处理,在反应速度上要比第一种慢一些。

       java与模式》书中提到,就java语言来说,第一种方式更符合java语言本身的特点。

       以上两种实现方式均失去了多态性,不允许被继承。还有另外一种灵活点的实现,将构造函数设置为受保护的,这样允许被继承产生子类。这种方式在具体实现上又有所不同,可以将父类中获得对象的静态方法放到子类中再实现;也可以在父类的静态方法中进行条件判断来决定获得哪一个对象;在GOF中认为最好的一种方式是维护一张存有对象和对应名称的注册表(可以使用HashMap来实现)。下面的实现参考《java与模式》采用带有注册表的方式。

 

import java.util.HashMap;

 

public class Singleton

{

//用来存放对应关系

       private static HashMap sinRegistry = new HashMap();

       static private Singleton s = new Singleton();

       //受保护的构造函数

       protected Singleton()

       {}

       public static Singleton getInstance(String name)

       {

              if(name == null)

                     name = "Singleton";

              if(sinRegistry.get(name)==null)

              {

                     try{

                            sinRegistry.put(name , Class.forName(name).newInstance());

                     }catch(Exception e)

                     {

                            e.printStackTrace();

                     }     

              }

              return (Singleton)(sinRegistry.get(name)); 

       }

       public void test()

       {

              System.out.println("getclasssuccess!");      

       }

}

 

public class SingletonChild1 extends Singleton

{

       public SingletonChild1(){}

       static    public SingletonChild1 getInstance()

       {

              return (SingletonChild1)Singleton.getInstance("SingletonChild1");     

       }

       public void test()

       {

              System.out.println("getclasssuccess111!"); 

       }

}

 

java中子类的构造函数的范围不能比父类的小,所以可能存在不守规则的客户程序使用其构造函数来产生实例。

 

四、单例模式邪恶论

看这题目也许有点夸张,不过这对初学者是一个很好的警告。单例模式在java中的使用存在很多陷阱和假象,这使得没有意识到单例模式使用局限性的你在系统中布下了隐患……

其实这个问题早在2001年的时候就有人在网上系统的提出来过,我在这里只是老生常谈了。但是对于大多的初学者来说,可能这样的观点在还很陌生。下面我就一一列举出单例模式在java中存在的陷阱。

 

多个虚拟机

当系统中的单例类被拷贝运行在多个虚拟机下的时候,在每一个虚拟机下都可以创建一个实例对象。在使用了EJBJINIRMI技术的分布式系统中,由于中间件屏蔽掉了分布式系统在物理上的差异,所以对你来说,想知道具体哪个虚拟机下运行着哪个单例对象是很困难的。

因此,在使用以上分布技术的系统中,应该避免使用存在状态的单例模式,因为一个有状态的单例类,在不同虚拟机上,各个单例对象保存的状态很可能是不一样的,问题也就随之产生。而且在EJB中不要使用单例模式来控制访问资源,因为这是由EJB容器来负责的。在其它的分布式系统中,当每一个虚拟机中的资源是不同的时候,可以考虑使用单例模式来进行管理。

 

多个类加载器

当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字节都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间(namespace)来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。

也许你认为出现多个类加载器的情况并不是很多。其实多个类加载器存在的情况并不少见。在很多J2EE服务器上允许存在多个servlet引擎,而每个引擎是采用不同的类加载器的;浏览器中applet小程序通过网络加载类的时候,由于安全因素,采用的是特殊的类加载器,等等。

       这种情况下,由状态的单例模式也会给系统带来隐患。因此除非系统由协调机制,在一般情况下不要使用存在状态的单例模式。

 

       错误的同步处理

       在使用上面介绍的懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能会达不到得到单个对象的效果,还可能引发死锁等错误。因此在使用懒汉式单例模式时一定要对同步有所了解。不过使用饿汉式单例模式就可以避免这个问题。

 

       子类破坏了对象控制

       在上一节介绍最后一种扩展性较好的单例模式实现方式的时候,就提到,由于类构造函数变得不再私有,就有可能失去对对象的控制。这种情况只能通过良好的文档来规范。

 

       串行化(可序列化)

为了使一个单例类变成可串行化的,仅仅在声明中添加“implements Serializable”是不够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入readResolve方法。关于这个方法的具体情况请参考《Effective Java》一书第57条建议。

其实对象的串行化并不仅局限于上述方式,还存在基于XML格式的对象串行化方式。这种方式也存在上述的问题,所以在使用的时候要格外小心。

 

       上面罗列了一些使用单例模式时可能会遇到的问题。而且这些问题都和java中的类、线程、虚拟机等基础而又复杂的概念交织在一起,你如果稍不留神……。但是这并不代表着单例模式就一无是处,更不能一棒子将其打死。它还是不可缺少的一种基础设计模式,它对一些问题提供了非常有效的解决方案,在java中你完全可以把它看成编码规范来学习,只是使用的时候要考虑周全些就可以了。

 

五、题外话

抛开单例模式,使用下面一种简单的方式也能得到单例,而且如果你确信此类永远是单例的,使用下面这种方式也许更好一些。

public static final Singleton INSTANCE = new Singleton();

而使用单例模式提供的方式,这可以在不改变API的情况下,改变我们对单例类的具体要求。

 

六、总结

竭尽所能写下了关于单例模式比较详细的介绍,请大家指正。


- 作者: crazybest 2006年09月29日, 星期五 15:58  回复(1) |  引用(0) 加入博采

单例模式完全剖析
概要
单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(classloaders)和序列化(serialization)时如何处理这些缺陷。

单例模式适合于一个类只有一个实例的情况,比如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个软件系统的不同对象访问,因此需要一个全局的访问指针,这便是众所周知的单例模式的应用。当然这只有在你确信你不再需要任何多于一个的实例的情况下。
单例模式的用意在于前一段中所关心的。通过单例模式你可以:

  • 确保一个类只有一个实例被建立
  • 提供了一个对对象的全局访问指针
  • 在不影响单例类的客户端的情况下允许将来有多个实例

    尽管单例设计模式如在下面的图中的所显示的一样是最简单的设计模式,但对于粗心的Java开发者来说却呈现出许多缺陷。这篇文章讨论了单例模式并揭示了那些缺陷。
    注意:你可以从Resources下载这篇文章的源代码。

    单例模式


    在《设计模式》一书中,作者这样来叙述单例模式的:确保一个类只有一个实例并提供一个对它的全局访问指针。
    下图说明了单例模式的类图。
    (图1)

    单例模式的类图

    正如你在上图中所看到的,这不是单例模式的完整部分。此图中单例类保持了一个对唯一的单例实例的静态引用,并且会从静态getInstance()方法中返回对那个实例的引用。
    例1显示了一个经典的单例模式的实现。
    例1.经典的单例模式
    1. public class ClassicSingleton {
    2.    private static ClassicSingleton instance = null;
    3.  
    4.    protected ClassicSingleton() {
    5.       // Exists only to defeat instantiation.
    6.    }
    7.    public static ClassicSingleton getInstance() {
    8.       if(instance == null) {
    9.          instance = new ClassicSingleton();
    10.       }
    11.       return instance;
    12.    }
    13. }

    在例1中的单例模式的实现很容易理解。ClassicSingleton类保持了一个对单独的单例实例的静态引用,并且从静态方法getInstance()中返回那个引用。
    关于ClassicSingleton类,有几个让我们感兴趣的地方。首先,ClassicSingleton使用了一个众所周知的懒汉式实例化去创建那个单例类的引用;结果,这个单例类的实例直到getInstance()方法被第一次调用时才被创建。这种技巧可以确保单例类的实例只有在需要时才被建立出来。其次,注意ClassicSingleton实现了一个protected的构造方法,这样客户端不能直接实例化一个ClassicSingleton类的实例。然而,你会惊奇的发现下面的代码完全合法:
    1. public class SingletonInstantiator { 
    2.   public SingletonInstantiator() { 
    3.    ClassicSingleton instance = ClassicSingleton.getInstance();
    4. ClassicSingleton anotherInstance =
    5. new ClassicSingleton();
    6.        ... 
    7.   } 
    8. }

    前面这个代码片段为何能在没有继承ClassicSingleton并且ClassicSingleton类的构造方法是protected的情况下创建其实例?答案是protected的构造方法可以被其子类以及在同一个包中的其它类调用。因为ClassicSingleton和SingletonInstantiator位于相同的包(缺省的包),所以SingletonInstantiator方法能创建ClasicSingleton的实例。
    这种情况下有两种解决方案:一是你可以使ClassicSingleton的构造方法变化私有的(private)这样只有ClassicSingleton的方法能调用它;然而这也意味着ClassicSingleton不能有子类。有时这是一种很合意的解决方法,如果确实如此,那声明你的单例类为final是一个好主意,这样意图明确,并且让编译器去使用一些性能优化选项。另一种解决方法是把你的单例类放到一个外在的包中,以便在其它包中的类(包括缺省的包)无法实例化一个单例类。
    关于ClassicSingleton的第三点感兴趣的地方是,如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
    第四点,如果ClasicSingleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
    最后也许是最重要的一点,就是例1中的ClassicSingleton类不是线程安全的。如果两个线程,我们称它们为线程1和线程2,在同一时间调用ClassicSingleton.getInstance()方法,如果线程1先进入if块,然后线程2进行控制,那么就会有ClassicSingleton的两个的实例被创建。

    正如你从前面的讨论中所看到的,尽管单例模式是最简单的设计模式之一,在Java中实现它也是决非想象的那么简单。这篇文章接下来会揭示Java规范对单例模式进行的考虑,但是首先让我们近水楼台的看看你如何才能测试你的单例类。
     

    测试单例模式


    接下来,我使用与log4j相对应的JUnit来测试单例类,它会贯穿在这篇文章余下的部分。如果你对JUnit或log4j不很熟悉,请参考相关资源。

    例2是一个用JUnit测试例1的单例模式的案例:
    例2.一个单例模式的案例
    1. import org.apache.log4j.Logger;
    2. import junit.framework.Assert;
    3. import junit.framework.TestCase;
    4.  
    5. public class SingletonTest extends TestCase {
    6.    private ClassicSingleton sone = null, stwo = null;
    7.    private static Logger logger = Logger.getRootLogger();
    8.  
    9.    public SingletonTest(String name) {
    10.       super(name);
    11.    }
    12.    public void setUp() {
    13.       logger.info("getting singleton...");
    14.       sone = ClassicSingleton.getInstance();
    15.       logger.info("...got singleton: " + sone);
    16.  
    17.       logger.info("getting singleton...");
    18.       stwo = ClassicSingleton.getInstance();
    19.       logger.info("...got singleton: " + stwo);
    20.    }
    21.    public void testUnique() {
    22.       logger.info("checking singletons for equality");
    23.       Assert.assertEquals(true, sone == stwo);
    24.    }
    25. }

    例2两次调用ClassicSingleton.getInstance(),并且把返回的引用存储在成员变量中。方法testUnique()会检查这些引用看它们是否相同。例3是这个测试案例的输出:
    例3.是这个测试案例的输出
    1. Buildfile: build.xml
    2.  
    3. init:
    4.      [echo] Build 20030414 (14-04-2003 03:08)
    5.  
    6. compile:
    7.  
    8. run-test-text:
    9.      [java] .INFO main: [b]getting singleton...[/b]
    10.      [java] INFO main: [b]created singleton:[/b] Singleton@e86f41
    11.      [java] INFO main: ...got singleton: Singleton@e86f41
    12.      [java] INFO main: [b]getting singleton...[/b]
    13.      [java] INFO main: ...got singleton: Singleton@e86f41
    14.      [java] INFO main: checking singletons for equality
    15.  
    16.      [java] Time: 0.032
    17.  
    18.      [java] OK (1 test)

    正如前面的清单所示,例2的简单测试顺利通过----通过ClassicSingleton.getInstance()获得的两个单例类的引用确实相同;然而,你要知道这些引用是在单线程中得到的。下面的部分着重于用多线程测试单例类。

    多线程因素的考虑


    在例1中的ClassicSingleton.getInstance()方法由于下面的代码而不是线程安全的:
    1. 1: if(instance == null) {
    2. 2:    instance = new Singleton();
    3. 3: }

    如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现(译注:在这可能是作者对很少出现这种情况而导致无法测试从而使人们放松警惕而感到叹惜)。为了演示这个线程轮换,我得重新实现例1中的那个类。例4就是修订后的单例类:
    例4.人为安排的方式
    1. import org.apache.log4j.Logger;
    2.  
    3. public class Singleton {
    4.   private static Singleton singleton = null;
    5.   private static Logger logger = Logger.getRootLogger();
    6.   private static boolean firstThread = true;
    7.  
    8.   protected Singleton() {
    9.     // Exists only to defeat instantiation.
    10.   }
    11.   public static Singleton getInstance() {
    12.      if(singleton == null) {
    13.         simulateRandomActivity();
    14.         singleton = new Singleton();
    15.      }
    16.      logger.info("created singleton: " + singleton);
    17.      return singleton;
    18.   }
    19.   private static void simulateRandomActivity() {
    20.      try {
    21.         if(firstThread) {
    22.            firstThread = false;
    23.            logger.info("sleeping...");
    24.  
    25.            // This nap should give the second thread enough time
    26.            // to get by the first thread.
    27.              Thread.currentThread().sleep(50);
    28.        }
    29.      }
    30.      catch(InterruptedException ex) {
    31.         logger.warn("Sleep interrupted");
    32.      }
    33.   }
    34. }

    除了在这个清单中的单例类强制使用了一个多线程错误处理,例4类似于例1中的单例类。在getInstance()方法第一次被调用时,调用这个方法的线程会休眠50毫秒以便另外的线程也有时间调用getInstance()并创建一个新的单例类实例。当休眠的线程觉醒时,它也会创建一个新的单例类实例,这样我们就有两个单例类实例。尽管例4是人为如此的,但它却模拟了第一个线程调用了getInstance()并在没有完成时被切换的真实情形。
    例5测试了例4的单例类:
    例5.失败的测试
    1. import org.apache.log4j.Logger;
    2. import junit.framework.Assert;
    3. import junit.framework.TestCase;
    4.  
    5. public class SingletonTest extends TestCase {
    6.    private static Logger logger = Logger.getRootLogger();
    7.    private static Singleton singleton = null;
    8.  
    9.    public SingletonTest(String name) {
    10.       super(name);
    11.    }
    12.    public void setUp() {
    13.       singleton = null;
    14.    }
    15.    public void testUnique() throws InterruptedException {
    16.       // Both threads call Singleton.getInstance().
    17.       Thread threadOne = new Thread(new SingletonTestRunnable()),
    18.              threadTwo = new Thread(new SingletonTestRunnable());
    19.  
    20.       threadOne.start();
    21.       threadTwo.start();
    22.  
    23.       threadOne.join();
    24.       threadTwo.join();
    25.    }
    26.    private static class SingletonTestRunnable implements Runnable {
    27.       public void run() {
    28.          // Get a reference to the singleton.
    29.          Singleton s = Singleton.getInstance();
    30.  
    31.          // Protect singleton member variable from
    32.          // multithreaded access.
    33.          synchronized(SingletonTest.class) {
    34.             if(singleton == null// If local reference is null...
    35.                singleton = s;     // ...set it to the singleton
    36.          }
    37.          // Local reference must be equal to the one and
    38.          // only instance of Singleton; otherwise, we have two
    39.                   // Singleton instances.
    40.          Assert.assertEquals(true, s == singleton);
    41.       }
    42.    }
    43. }

    例5的测试案例创建两个线程,然后各自启动,等待完成。这个案例保持了一个对单例类的静态引用,每个线程都会调用Singleton.getInstance()。如果这个静态成员变量没有被设置,那么第一个线程就会将它设为通过调用getInstance()而得到的引用,然后这个静态变量会与一个局部变量比较是否相等。
    在这个测试案例运行时会发生一系列的事情:第一个线程调用getInstance(),进入if块,然后休眠;接着,第二个线程也调用getInstance()并且创建了一个单例类的实例。第二个线程会设置这个静态成员变量为它所创建的引用。第二个线程检查这个静态成员变量与一个局部备份的相等性。然后测试通过。当第一个线程觉醒时,它也会创建一个单例类的实例,并且它不会设置那个静态成员变量(因为第二个线程已经设置过了),所以那个静态变量与那个局部变量脱离同步,相等性测试即告失败。例6列出了例5的输出:
    例6.例5的输出
    1. Buildfile: build.xml
    2. init:
    3.      [echo] Build 20030414 (14-04-2003 03:06)
    4. compile:
    5. run-test-text:
    6. INFO Thread-1: sleeping...
    7. INFO Thread-2: created singleton: Singleton@7e5cbd
    8. INFO Thread-1: created singleton: Singleton@704ebb
    9. junit.framework.AssertionFailedError: expected: but was:
    10.    at junit.framework.Assert.fail(Assert.java:47)
    11.    at junit.framework.Assert.failNotEquals(Assert.java:282)
    12.    at junit.framework.Assert.assertEquals(Assert.java:64)
    13.    at junit.framework.Assert.assertEquals(Assert.java:149)
    14.    at junit.framework.Assert.assertEquals(Assert.java:155)
    15.    at SingletonTest$SingletonTestRunnable.run(Unknown Source)
    16.    at java.lang.Thread.run(Thread.java:554)
    17.      [java] .
    18.      [java] Time: 0.577
    19.  
    20.      [java] OK (1 test)

    到现在为止我们已经知道例4不是线程安全的,那就让我们看看如何修正它。

    同步


    要使例4的单例类为线程安全的很容易----只要像下面一个同步化getInstance()方法:
    1. public synchronized static Singleton getInstance() {
    2.    if(singleton == null) {
    3.       simulateRandomActivity();
    4.       singleton = new Singleton();
    5.    }
    6.    logger.info("created singleton: " + singleton);
    7.    return singleton;
    8. }

    在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果:
    1.  
    2. Buildfile: build.xml
    3.  
    4. init:
    5.      [echo] Build 20030414 (14-04-2003 03:15)
    6.  
    7. compile:
    8.     [javac] Compiling 2 source files
    9.  
    10. run-test-text:
    11. INFO Thread-1: sleeping...
    12. INFO Thread-1: created singleton: Singleton@ef577d
    13. INFO Thread-2: created singleton: Singleton@ef577d
    14.      [java] .
    15.      [java] Time: 0.513
    16.  
    17.      [java] OK (1 test)

    这此,这个测试案例工作正常,并且多线程的烦恼也被解决;然而,机敏的读者可能会认识到getInstance()方法只需要在第一次被调用时同步。因为同步的性能开销很昂贵(同步方法比非同步方法能降低到100次左右),或许我们可以引入一种性能改进方法,它只同步单例类的getInstance()方法中的赋值语句。

    一种性能改进的方法


    寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法:
    1.  
    2. public static Singleton getInstance() {
    3.    if(singleton == null) {
    4.       synchronized(Singleton.class) { 
    5.          singleton = new Singleton();
    6.       }
    7.    }
    8.    return singleton;
    9. }

    这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。

    双重加锁检查


    初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术:
    1. public static Singleton getInstance() {
    2.   if(singleton == null) {
    3.      synchronized(Singleton.class) {
    4.        if(singleton == null) {
    5.          singleton = new Singleton();
    6.        }
    7.     }
    8.   }
    9.   return singleton;
    10. }

    如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。
    不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。

    一个改进的线程安全的单例模式实现


    例7列出了一个简单、快速而又是线程安全的单例模式实现:
    例7.一个简单的单例类
    1. public class Singleton {
    2.    public final static Singleton INSTANCE = new Singleton();
    3.    private Singleton() {
    4.          // Exists only to defeat instantiation.
    5.       }
    6. }

    这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它:
    1.       Singleton singleton = Singleton.INSTANCE;
    2.       singleton.dothis();
    3.       singleton.dothat();
    4.       ...

    当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做.

    你可以安全的使用例7的单例模式实现或者是例1的带一个同步的getInstance()方法的实现.然而,我们必须要研究另一个问题:你必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类.
     

    使用注册表


    使用一个单例类注册表可以:
  • 在运行期指定单例类
  • 防止产生多个单例类子类的实例
    在例8的单例类中,保持了一个通过类名进行注册的单例类注册表:
    例8 带注册表的单例类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class Singleton {
    5.    private static HashMap map = new HashMap();
    6.    private static Logger logger = Logger.getRootLogger();
    7.  
    8.    protected Singleton() {
    9.       // Exists only to thwart instantiation
    10.    }
    11.    public static synchronized Singleton getInstance(String classname) {
    12.       if(classname == nullthrow new IllegalArgumentException("Illegal classname");
    13.          Singleton singleton = (Singleton)map.get(classname);
    14.  
    15.       if(singleton != null) {
    16.          logger.info("got singleton from map: " + singleton);
    17.          return singleton;
    18.       }
    19.       if(classname.equals("SingeltonSubclass_One"))
    20.             singleton = new SingletonSubclass_One();         
    21.          else if(classname.equals("SingeltonSubclass_Two"))
    22.             singleton = new SingletonSubclass_Two();
    23.  
    24.       map.put(classname, singleton);
    25.       logger.info("created singleton: " + singleton);
    26.       return singleton;
    27.    }
    28.    // Assume functionality follows that's attractive to inherit
    29. }

    这段代码的基类首先创建出子类的实例,然后把它们存储在一个Map中。但是基类却得付出很高的代价因为你必须为每一个子类替换它的getInstance()方法。幸运的是我们可以使用反射处理这个问题。

    使用反射


    在例9的带注册表的单例类中,使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现,Singleton.getInstance()方法不需要在每个被实现的子类中重写了。
    例9 使用反射实例化单例类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class Singleton {
    5.    private static HashMap map = new HashMap();
    6.    private static Logger logger = Logger.getRootLogger();
    7.  
    8.    protected Singleton() {
    9.       // Exists only to thwart instantiation
    10.    }
    11.    public static synchronized Singleton getInstance(String classname) {
    12.       Singleton singleton = (Singleton)map.get(classname);
    13.  
    14.       if(singleton != null) {
    15.          logger.info("got singleton from map: " + singleton);
    16.          return singleton;
    17.       }
    18.       try {
    19.          singleton = (Singleton)Class.forName(classname).newInstance();
    20.       }
    21.       catch(ClassNotFoundException cnf) {
    22.          logger.fatal("Couldn't find class " + classname);    
    23.       }
    24.       catch(InstantiationException ie) {
    25.          logger.fatal("Couldn't instantiate an object of type " + classname);    
    26.       }
    27.       catch(IllegalAccessException ia) {
    28.          logger.fatal("Couldn't access class " + classname);    
    29.       }
    30.       map.put(classname, singleton);
    31.       logger.info("created singleton: " + singleton);
    32.  
    33.       return singleton;
    34.    }
    35. }


    关于单例类的注册表应该说明的是:它们应该被封装在它们自己的类中以便最大限度的进行复用。

    封装注册表


    例10列出了一个单例注册表类。
    例10 一个SingletonRegistry类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class SingletonRegistry {
    5.    public static SingletonRegistry REGISTRY = new SingletonRegistry();
    6.  
    7.    private static HashMap map = new HashMap();
    8.    private static Logger logger = Logger.getRootLogger();
    9.  
    10.    protected SingletonRegistry() {
    11.       // Exists to defeat instantiation
    12.    }
    13.    public static synchronized Object getInstance(String classname) {
    14.       Object singleton = map.get(classname);
    15.  
    16.       if(singleton != null) {
    17.          return singleton;
    18.       }
    19.       try {
    20.          singleton = Class.forName(classname).newInstance();
    21.          logger.info("created singleton: " + singleton);
    22.       }
    23.       catch(ClassNotFoundException cnf) {
    24.          logger.fatal("Couldn't find class " + classname);    
    25.       }
    26.       catch(InstantiationException ie) {
    27.          logger.fatal("Couldn't instantiate an object of type " + 
    28.                        classname);    
    29.       }
    30.       catch(IllegalAccessException ia) {
    31.          logger.fatal("Couldn't access class " + classname);    
    32.       }
    33.       map.put(classname, singleton);
    34.       return singleton;
    35.    }
    36. }

    注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。
    例11 使用了一个封装的注册表的Singleton类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class Singleton {
    5.  
    6.    protected Singleton() {
    7.       // Exists only to thwart instantiation.
    8.    }
    9.    public static Singleton getInstance() {
    10.       return (Singleton)SingletonRegistry.REGISTRY.getInstance(classname);
    11.    }
    12. }

    上面的Singleton类使用那个注册表的唯一实例通过类名取得单例对象。
    现在我们已经知道如何实现线程安全的单例类和如何使用一个注册表去在运行期指定单例类名,接着让我们考查一下如何安排类载入器和处理序列化。

    Classloaders


    在许多情况下,使用多个类载入器是很普通的--包括servlet容器--所以不管你在实现你的单例类时是多么小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入,那你就必须自己指定这个类载入器;例如:
    1. private static Class getClass(String classname) 
    2.                                          throws ClassNotFoundException {
    3.       ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    4.  
    5.       if(classLoader == null)
    6.          classLoader = Singleton.class.getClassLoader();
    7.  
    8.       return (classLoader.loadClass(classname));
    9.    }
    10. }

    这个方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null,这个方法会使用与装入单例类基类的那个类载入器。这个方法可以用Class.forName()代替。

    序列化


    如果你序列化一个单例类,然后两次重构它,那么你就会得到那个单例类的两个实例,除非你实现readResolve()方法,像下面这样:
    例12 一个可序列化的单例类
    1. import org.apache.log4j.Logger;
    2.  
    3. public class Singleton implements java.io.Serializable {
    4.    public static Singleton INSTANCE = new Singleton();
    5.  
    6.    protected Singleton() {
    7.       // Exists only to thwart instantiation.
    8.    }
    9. [b]      private Object readResolve() {
    10.             return INSTANCE;
    11.       }[/b]}

    上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构,它都只会返回那个相同的单例类实例。
    例13测试了例12的单例类:
    例13 测试一个可序列化的单例类
    1. import java.io.*;
    2. import org.apache.log4j.Logger;
    3. import junit.framework.Assert;
    4. import junit.framework.TestCase;
    5.  
    6. public class SingletonTest extends TestCase {
    7.    private Singleton sone = null, stwo = null;
    8.    private static Logger logger = Logger.getRootLogger();
    9.  
    10.    public SingletonTest(String name) {
    11.       super(name);
    12.    }
    13.    public void setUp() {
    14.       sone = Singleton.INSTANCE;
    15.       stwo = Singleton.INSTANCE;
    16.    }
    17.    public void testSerialize() {
    18.       logger.info("testing singleton serialization...");
    19. [b]      writeSingleton();
    20.       Singleton s1 = readSingleton();
    21.       Singleton s2 = readSingleton();
    22.       Assert.assertEquals(true, s1 == s2);[/b]   }
    23.    private void writeSingleton() {
    24.       try {
    25.          FileOutputStream fos = new FileOutputStream("serializedSingleton");
    26.          ObjectOutputStream oos = new ObjectOutputStream(fos);
    27.          Singleton s = Singleton.INSTANCE;
    28.  
    29.          oos.writeObject(Singleton.INSTANCE);
    30.          oos.flush();
    31.       }
    32.       catch(NotSerializableException se) {
    33.          logger.fatal("Not Serializable Exception: " + se.getMessage());
    34.       }
    35.       catch(IOException iox) {
    36.          logger.fatal("IO Exception: " + iox.getMessage());
    37.       }
    38.    }
    39.    private Singleton readSingleton() {
    40.       Singleton s = null;
    41.  
    42.       try {
    43.          FileInputStream fis = new FileInputStream("serializedSingleton");
    44.          ObjectInputStream ois = new ObjectInputStream(fis);
    45.          s = (Singleton)ois.readObject();
    46.       }
    47.       catch(ClassNotFoundException cnf) {
    48.          logger.fatal("Class Not Found Exception: " + cnf.getMessage());
    49.       }
    50.       catch(NotSerializableException se) {
    51.          logger.fatal("Not Serializable Exception: " + se.getMessage());
    52.       }
    53.       catch(IOException iox) {
    54.          logger.fatal("IO Exception: " + iox.getMessage());
    55.       }
    56.       return s;
    57.    }
    58.    public void testUnique() {
    59.       logger.info("testing singleton uniqueness...");
    60.       Singleton another = new Singleton();
    61.  
    62.       logger.info("checking singletons for equality");
    63.       Assert.assertEquals(true, sone == stwo);
    64.    }
    65. }

    前面这个测试案例序列化例12中的单例类,并且两次重构它。然后这个测试案例检查看是否被重构的单例类实例是同一个对象。下面是测试案例的输出:
    1.  
    2. Buildfile: build.xml
    3.  
    4. init:
    5.      [echo] Build 20030422 (22-04-2003 11:32)
    6.  
    7. compile:
    8.  
    9. run-test-text:
    10.      [java] .INFO main: testing singleton serialization...
    11.      [java] .INFO main: testing singleton uniqueness...
    12.      [java] INFO main: checking singletons for equality
    13.  
    14.      [java] Time: 0.1
    15.  
    16.      [java] OK (2 tests)

    单例模式结束语


    单例模式简单却容易让人迷惑,特别是对于Java的开发者来说。在这篇文章中,作者演示了Java开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类。
  • - 作者: crazybest 2006年09月29日, 星期五 15:44  回复(0) |  引用(0) 加入博采

    UNIX 下让 Oracle 定时执行 *.sql 文件
     ORACLE数据库自带的DBMS_JOB功能可以实现定时执行PL/SQL的存储过程,但是如果SQL语句很复杂,SQL语句很多,以及经常要改变SQL语句的写法,用写PL/SQL存储过程的方法再定时执行会比较繁琐。何况还有一些UNIX系统管理员不会写PL/SQL存储过程,所以我介绍一个简单的shell程序可以在安装了ORACLE SERVER或CLIENT的UNIX机器上实现定时执行一个*.sql文件。
      首先我们在安装了ORACLE SERVER或CLIENT的UNIX机器上连接目的数据库:
              $sqlplus username/password@servie_name
      如果能够成功进入SQL>状态,并执行简单的SQL语句
            SQL> SELECT SYSDATE FROM DUAL;
      表明连接成功。
            
      否则检查/$ORACLE_HOME/network/admin/tnsnames.ora    里servie_name是否正确定义
                            /etc/hostname                   里是否包含目的数据库的主机名
                            
            等等......(其它的网络检查就不在这里详细列举了)
            
            接着在scott用户下运行测试的SQL语句:scott_select.sql
            
            SQL> SELECT D.DNAME,E.ENAME,E.JOB,E.HIREDATE 
                            FROM EMP E,DEPT D 
                            WHERE TO_CHAR(E.HIREDATE,'YYYY')='1981' AND E.DEPTNO=D.DEPTNO;
            
            然后在目录/oracle_backup/bin/下写一个类似下面的shell文件scott_select.sh
            ------------------------------------------------------------------------                
            su - oracle -c "sqlplus -s scott/tiger@servie_name"<<EOF
            spool /oracle_backup/log/scott_select.txt;
            @/oracle_backup/bin/scott_select.sql;
            spool off;
            exit;
            -------------------------------------------------------------------------       
            说明:
                spool语句把scott_select.sql语句的执行结果输出到/oracle_backup/log/scott_select.txt文件
                @符号是执行/oracle_backup/bin/scott_select.sql文件
                在要执行的*.sql文件里可以存放DML、DDL等多条SQL语句。
            
            改变scott_select.sh的属性成755, 可以执行   
            $chmod 755 /oracle_backup/bin/scott_select.sh
            
            这样,UNIX系统管理员(root权限)可以利用crontab命令把scott_select.sh加入定时操作队列里。
            或者直接编辑OS下的配置文件:
            
            Sun Solaris     文件      /var/spool/cron/crontabs/root
            Linux           文件      /var/spool/cron/root
            
            在root文件后面添加一行(含义:每月的18日4:40分执行scott_select.sh)
            40 4 18 * * /oracle_backup/bin/scott_select.sh
      
            时间表按顺序是:分钟(0-59) 小时(0-23) 日期(1-31) 月份(1-12) 星期几(0-6)
            您可以根据不同的需求来组合它们。
            
            如果想每2分钟(偶数)执行一次, 分钟栏里是这样写 0-59/2
            
            如果想每2分钟(单数)执行一次, 分钟栏里是这样写 1-59/2
            
            依此类推,时间表定时的栏位总是在起始时间开始,间隔除数的的单位时间后继续,直到终止值。
    
            重新启动OS的定时服务,使新添加的任务生效
            Sun Solaris
            #/etc/rc2.d/S75cron stop
            #/etc/rc2.d/S75cron start
            
            Linux
            #/etc/rc.d/init.d/crond restart 
            
            这样ORACLE数据库就会定时执行scott_select.sql文件,并把结果输出到OS文件scott_select.txt。
            
            如果我们要新写或者修改scott_select.sql文件,直接编辑它就可以了。 
    

    - 作者: crazybest 2006年08月29日, 星期二 15:48  回复(0) |  引用(0) 加入博采

    Oracle 自动启动脚本的编写
    在Linux RedHat AS 3.0 Update5上安装完Oracle 9i后,发现当主机重新启动后,Oralce不能自动重新启动,而且监听程序和Oracle Web Server都不能自动启动,按照下面的方法进行了设置,能够达到自动启动Oracle数据库以及监听程序和Web Server的效果。

    1. 在文件/etc/oratab中添加
    # 添加如下内容到/etc/oratab文件中,
    # $ORACLE_SID是你的Oracle数据库的sid
    # $ORACLE_HOME是你的Oracle数据库的Oracle_home
    # Y表示要求在系统启动的时候启动Oracle数据库.N表示不要在系统启动的时候启动Oracle
    $ORACLE_SID:$ORACLE_HOME:Y
    2. 修改文件/etc/rc.local添加一下两行
    ## 关于su的具体命令参看linux的manual文档
    ## dbstart: 启动Oracle数据库
    ## lsnrctl: 启动Oracle数据库监听程序
    ## $ORACLE_HOME/Apache/Apache/bin/startJServ.sh: 启动Oracle Web Server 7777端口
    su - oracle -c 'dbstart'
    su - oracle -c 'lsnrctl start'
    su - oracle -c '$ORACLE_HOME/Apache/Apache/bin/startJServ.sh'
    3. 在本机使用dbstart,和dbshut测试设置是否准确.
    [oracle@tzcenter oracle]$ dbshut
    SQL*Plus: Release 9.2.0.4.0 - Production on Thu Nov 11 21:02:35 2004 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
    SQL> Connected.
    SQL> Database closed.
    Database dismounted.
    ORACLE instance shut down.
    SQL> Disconnected from Oracle9i Enterprise Edition Release 9.2.0.4.0 Production With the Partitioning, OLAP and Oracle Data Mining options JServer Release 9.2.0.4.0 - Production
    Database "webora9" shut down.

    [oracle@tzcenter oracle]$ dbstart
    SQL*Plus: Release 9.2.0.4.0 - Production on Thu Nov 11 21:02:43 2004 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
    SQL> Connected to an idle instance.
    SQL> ORACLE instance started.
    Total System Global Area 437327188 bytes
    Fixed Size 451924 bytes
    Variable Size 134217728 bytes
    Database Buffers 301989888 bytes
    Redo Buffers 667648 bytes
    Database mounted.
    Database opened.
    SQL> Disconnected from Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production With the Partitioning, OLAP and Oracle Data Mining options JServer Release 9.2.0.4.0 - Production
    Database "webora9" warm started.
    [oracle@tzcenter oracle]$
    注意:dbstart 脚本里面用的是 pfile 来启动数据库的,所以需要连接到数据库产生pfile文件,才能直接使用dbstart命令启动并连接数据库,具体方法:
    [oracle@tzcenter oracle]$ sqlplus '/ as sysdba'
    SQL*Plus: Release 9.2.0.4.0 - Production on Thu Nov 11 21:02:43 2004 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
    SQL>connect internal/oracle
    SQL>create pfile from spfile
    [oracle@tzcenter oracle]$

    - 作者: crazybest 2006年08月29日, 星期二 15:47  回复(0) |  引用(0) 加入博采

    深刻理解Oracle数据库的启动和关闭 [转载]

    Oracle数据库提供了几种不同的数据库启动和关闭方式,本文将详细介绍这些启动和关闭方式之间的区别以及它们各自不同的功能。


    一、启动和关闭Oracle数据库

      对于大多数Oracle DBA来说,启动和关闭Oracle数据库最常用的方式就是在命令行方式下的Server Manager。从Oracle 8i以后,系统将Server Manager的所有功能都集中到了SQL*Plus中,也就是说从8i以后对于数据库的启动和关闭可以直接通过SQL*Plus来完成,而不再另外需要 Server Manager,但系统为了保持向下兼容,依旧保留了Server Manager工具。另外也可通过图形用户工具(GUI)的Oracle Enterprise Manager来完成系统的启动和关闭,图形用户界面Instance Manager非常简单,这里不再详述。

      要启动和关闭数据库,必须要以具有Oracle 管理员权限的用户登陆,通常也就是以具有SYSDBA权限的用户登陆。一般我们常用INTERNAL用户来启动和关闭数据库(INTERNAL用户实际上是SYS用户以SYSDBA连接的同义词)。Oracle数据库的新版本将逐步淘汰INTERNAL这个内部用户,所以我们最好还是设置DBA用户具有 SYSDBA权限。

    二、数据库的启动(STARTUP)

    启动一个数据库需要三个步骤:

      1、 创建一个Oracle实例(非安装阶段)
      2、 由实例安装数据库(安装阶段)
      3、 打开数据库(打开阶段)

    在Startup命令中,可以通过不同的选项来控制数据库的不同启动步骤。

      1、STARTUP NOMOUNT

      NONOUNT选项仅仅创建一个Oracle实例。读取init.ora初始化参数文件、启动后台进程、初始化系统全局区(SGA)。Init.ora文件定义了实例的配置,包括内存结构的大小和启动后台进程的数量和类型等。实例名根据Oracle_SID设置,不一定要与打开的数据库名称相同。当实例打开后,系统将显示一个SGA内存结构和大小的列表,如下所示:

    SQL> startup nomount
      ORACLE 例程已经启动。
      Total System Global Area 35431692 bytes
      Fixed Size 70924 bytes
      Variable Size 18505728 bytes
      Database Buffers 16777216 bytes
      Redo Buffers 77824 bytes

    2、STARTUP MOUNT

      该命令创建实例并且安装数据库,但没有打开数据库。Oracle系统读取控制文件中关于数据文件和重作日志文件的内容,但并不打开该文件。这种打开方式常在数据库维护操作中使用,如对数据文件的更名、改变重作日志以及打开归档方式等。在这种打开方式下,除了可以看到SGA系统列表以外,系统还会给出"数据库装载完毕"的提示。

    3、STARTUP

      该命令完成创建实例、安装实例和打开数据库的所有三个步骤。此时数据库使数据文件和重作日志文件在线,通常还会请求一个或者是多个回滚段。这时系统除了可以看到前面Startup Mount方式下的所有提示外,还会给出一个"数据库已经打开"的提示。此时,数据库系统处于正常工作状态,可以接受用户请求。

      如果采用STARTUP NOMOUNT或者是STARTUP MOUNT的数据库打开命令方式,必须采用ALTER DATABASE命令来执行打开数据库的操作。例如,如果你以STARTUP NOMOUNT方式打开数据库,也就是说实例已经创建,但是数据库没有安装和打开。这是必须运行下面的两条命令,数据库才能正确启动。

      ALTER DATABASE MOUNT;
      ALTER DATABASE OPEN;

    而如果以STARTUP MOUNT方式启动数据库,只需要运行下面一条命令即可以打开数据库:

      ALTER DATABASE OPEN.

    4、其他打开方式

    除了前面介绍的三种数据库打开方式选项外,还有另外其他的一些选项。

    (1) STARTUP RESTRICT

      这种方式下,数据库将被成功打开,但仅仅允许一些特权用户(具有DBA角色的用户)才可以使用数据库。这种方式常用来对数据库进行维护,如数据的导入/导出操作时不希望有其他用户连接到数据库操作数据。

    (2) STARTUP FORCE

      该命令其实是强行关闭数据库(shutdown abort)和启动数据库(startup)两条命令的一个综合。该命令仅在关闭数据库遇到问题不能关闭数据库时采用。

    (3) ALTER DATABASE OPEN READ ONLY;

      该命令在创建实例以及安装数据库后,以只读方式打开数据库。对于那些仅仅提供查询功能的产品数据库可以采用这种方式打开。

    三、数据库的关闭(SHUTDOWN)

    对于数据库的关闭,有四种不同的关闭选项,下面对其进行一一介绍。

    1、SHUTDOWN NORMAL

      这是数据库关闭SHUTDOWN命令的确省选项。也就是说如果你发出SHUTDOWN这样的命令,也即是SHUTDOWN NORNAL的意思。

      发出该命令后,任何新的连接都将再不允许连接到数据库。在数据库关闭之前,Oracle将等待目前连接的所有用户都从数据库中退出后才开始关闭数据库。采用这种方式关闭数据库,在下一次启动时不需要进行任何的实例恢复。但需要注意一点的是,采用这种方式,也许关闭一个数据库需要几天时间,也许更长。

    2、SHUTDOWN IMMEDIATE

      这是我们常用的一种关闭数据库的方式,想很快地关闭数据库,但又想让数据库干净的关闭,常采用这种方式。

      当前正在被Oracle处理的SQL语句立即中断,系统中任何没有提交的事务全部回滚。如果系统中存在一个很长的未提交的事务,采用这种方式关闭数据库也需要一段时间(该事务回滚时间)。系统不等待连接到数据库的所有用户退出系统,强行回滚当前所有的活动事务,然后断开所有的连接用户。

    3、SHUTDOWN TRANSACTIONAL

      该选项仅在Oracle 8i后才可以使用。该命令常用来计划关闭数据库,它使当前连接到系统且正在活动的事务执行完毕,运行该命令后,任何新的连接和事务都是不允许的。在所有活动的事务完成后,数据库将和SHUTDOWN IMMEDIATE同样的方式关闭数据库。

    4、SHUTDOWN ABORT

      这是关闭数据库的最后一招,也是在没有任何办法关闭数据库的情况下才不得不采用的方式,一般不要采用。如果下列情况出现时可以考虑采用这种方式关闭数据库。

    1、 数据库处于一种非正常工作状态,不能用shutdown normal或者shutdown immediate这样的命令关闭数据库;

    2、 需要立即关闭数据库;

    3、 在启动数据库实例时遇到问题;

      所有正在运行的SQL语句都将立即中止。所有未提交的事务将不回滚。Oracle也不等待目前连接到数据库的用户退出系统。下一次启动数据库时需要实例恢复,因此,下一次启动可能比平时需要更多的时间。

      表1可以清楚地看到上述四种不同关闭数据库的区别和联系。

            表1 Shutdown数据库不同方式对比表

          关闭方式 A I T N
          允许新的连接 × × × ×
          等待直到当前会话中止 × × × √
          等待直到当前事务中止 × × √ √
          强制CheckPoint,关闭所有文件 × √ √ √

    其中:A-Abort I-Immediate T-Transaction N-Nornal

    - 作者: crazybest 2006年08月29日, 星期二 15:46  回复(0) |  引用(0) 加入博采

    Java线程总结[转载]

                                        

    首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多任务,分时操作系统。正是由于这种操作系统的出现才有了多线程这个概念。我们使用的windows,linux就属于此列。什么是分时操作系统呢,通俗一点与就是可以同一时间执行多个程序的操作系统,在自己的电脑上面,你是不是一边听歌,一边聊天还一边看网页呢?但实际上,并不上cpu在同时执行这些程序,cpu只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在cpu的高速计算能力,给人的感觉就像是多个程序在同时执行一样。
    一般可以在同一时间内执行多个程序的操作系统都有进程的概念.一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,每一个进程的内部数据和状态都是完全独立的.因此可以想像创建并执行一个进程的系统开像是比较大的,所以线程出现了。在java中,程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务.多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行.(你可以将前面一句话的程序换成进程,进程是程序的一次执行过程,是系统运行程序的基本单位)

    线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈.所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-weight process).一个进程中可以包含多个线程.

    多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期.用线程的状态(state)表明线程处在生命周期的哪个阶段.线程有创建,可运行,运行中,阻塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每个程序至少自动拥有一个线程,称为主线程.当程序加载到内存时,启动主线程.

    [线程的运行机制以及调度模型]
    java中多线程就是一个类或一个程序执行或管理多个线程执行任务的能力,每个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制着它的所有线程,可以决定哪个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。
    下面是线程的机制图:


    线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务.线程有创建,可运行,运行中,阻塞,死亡五中状态.一个具有生命的线程,总是处于这五种状态之一:
    1.创建状态
    使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread)
    2.可运行状态
    使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)
    3.运行中状态
    Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running).此时,系统真正执行线程的run()方法.
    4.阻塞状态
    一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)
    5.死亡状态
    线程结束后是死亡状态(Dead)

    同一时刻如果有多个线程处于可运行状态,则他们需要排队等待CPU资源.此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度.可运行状态的线程按优先级排队,线程调度依据优先级基础上的"先到先服务"原则.
    线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度.当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态.

    线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行.先占式调度分为:独占式和分时方式.
    独占方式下,当前执行线程将一直执行下去,直 到执行完毕或由于某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占
    分时方式下,当前运行线程获得一个时间片,时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度.系统选中其他可运行状态的线程执行
    分时方式的系统使每个线程工作若干步,实现多线程同时运行

    另外请注意下面的线程调度规则(如果有不理解,不急,往下看):
    ①如果两个或是两个以上的线程都修改一个对象,那么把执行修改的方法定义为被同步的(Synchronized),如果对象更新影响到只读方法,那么只度方法也应该定义为同步的
    ②如果一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部等待,它可以调用一个被同步的方法,并让这个方法调用wait()
    ③每当一个方法改变某个对象的状态的时候,它应该调用notifyAll()方法,这给等待队列的线程提供机会来看一看执行环境是否已发生改变
    ④记住wait(),notify(),notifyAll()方法属于Object类,而不是Thread类,仔细检查看是否每次执行wait()方法都有相应的notify()或notifyAll()方法,且它们作用与相同的对象 在java中每个类都有一个主线程,要执行一个程序,那么这个类当中一定要有main方法,这个man方法也就是java class中的主线程。你可以自己创建线程,有两种方法,一是继承Thread类,或是实现Runnable接口。一般情况下,最好避免继承,因为java中是单根继承,如果你选用继承,那么你的类就失去了弹性,当然也不能全然否定继承Thread,该方法编写简单,可以直接操作线程,适用于单重继承情况。至于选用那一种,具体情况具体分析。


    eg.继承Thread

    public class MyThread_1 extends Thread
    {
    public void run()
    {
    //some code
    }
    }


    eg.实现Runnable接口
    public class MyThread_2 implements Runnable
    {
    public void run()
    {
    //some code
    }
    }



    当使用继承创建线程,这样启动线程:
    new MyThread_1().start()


    当使用实现接口创建线程,这样启动线程:
    new Thread(new MyThread_2()).start()


    注意,其实是创建一个线程实例,并以实现了Runnable接口的类为参数传入这个实例,当执行这个线程的时候,MyThread_2中run里面的代码将被执行。
    下面是完成的例子:

    public class MyThread implements Runnable
    {

    public void run()
    {
    System.out.println("My Name is "+Thread.currentThread().getName());
    }
    public static void main(String[] args)
    {
    new Thread(new MyThread()).start();
    }
    }



    执行后将打印出:
    My Name is Thread-0

    你也可以创建多个线程,像下面这样
    new Thread(new MyThread()).start();
    new Thread(new MyThread()).start();
    new Thread(new MyThread()).start();



    那么会打印出:
    My Name is Thread-0
    My Name is Thread-1
    My Name is Thread-2


    看了上面的结果,你可能会认为线程的执行顺序是依次执行的,但是那只是一般情况,千万不要用以为是线程的执行机制;影响线程执行顺序的因素有几点:首先看看前面提到的优先级别


    public class MyThread implements Runnable
    {

    public void run()
    {
    System.out.println("My Name is "+Thread.currentThread().getName());
    }
    public static void main(String[] args)
    {
    Thread t1=new Thread(new MyThread());
    Thread t2=new Thread(new MyThread());
    Thread t3=new Thread(new MyThread());
    t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先级
    t1.start();
    t2.start();
    t3.start();
    }
    }


    再看看结果:
    My Name is Thread-1
    My Name is Thread-0
    My Name is Thread-2



    线程的优先级分为10级,分别用1到10的整数代表,默认情况是5。上面的t2.setPriority(Thread.MAX_PRIORITY)等价与t2.setPriority(10)
    然后是线程程序本身的设计,比如使用sleep,yield,join,wait等方法(详情请看JDKDocument)

    public class MyThread implements Runnable
    {
    public void run()
    {
    try
    {
    int sleepTime=(int)(Math.random()*100);//产生随机数字,
    Thread.currentThread().sleep(sleepTime);//让其休眠一定时间,时间又上面sleepTime决定
    //public static void sleep(long millis)throw InterruptedException (API)
    System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime);
    }catch(InterruptedException ie)//由于线程在休眠可能被中断,所以调用sleep方法的时候需要捕捉异常
    {
    ie.printStackTrace();
    }
    }
    public static void main(String[] args)
    {
    Thread t1=new Thread(new MyThread());
    Thread t2=new Thread(new MyThread());
    Thread t3=new Thread(new MyThread());
    t1.start();
    t2.start();
    t3.start();
    }
    }


    执行后观察其输出:

    Thread-0 睡了 11
    Thread-2 睡了 48
    Thread-1 睡了 69




    上面的执行结果是随机的,再执行很可能出现不同的结果。由于上面我在run中添加了休眠语句,当线程休眠的时候就会让出cpu,cpu将会选择执行处于runnable状态中的其他线程,当然也可能出现这种情况,休眠的Thread立即进入了runnable状态,cpu再次执行它。
    [线程组概念]
    线程是可以被组织的,java中存在线程组的概念,每个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组可以同时对其中的多个线程进行操作,如启动一个线程组的所有线程等.Java的线程组由java.lang包中的Thread——Group类实现.
    ThreadGroup类用来管理一组线程,包括:线程的数目,线程间的关系,线程正在执行的操作,以及线程将要启动或终止时间等.线程组还可以包含线程组.在Java的应用程序中,最高层的线程组是名位main的线程组,在main中还可以加入线程或线程组,在mian的子线程组中也可以加入线程和线程组,形成线程组和线程之间的树状继承关系。像上面创建的线程都是属于main这个线程组的。
    借用上面的例子,main里面可以这样写:

    public static void main(String[] args)
    {
    /***************************************
    ThreadGroup(String name)
    ThreadGroup(ThreadGroup parent, String name)
    ***********************************/
    ThreadGroup group1=new ThreadGroup("group1");
    ThreadGroup group2=new ThreadGroup(group1,"group2");
    Thread t1=new Thread(group2,new MyThread());
    Thread t2=new Thread(group2,new MyThread());
    Thread t3=new Thread(group2,new MyThread());
    t1.start();
    t2.start();
    t3.start();
    }



    线程组的嵌套,t1,t2,t3被加入group2,group2加入group1。
    另外一个比较多就是关于线程同步方面的,试想这样一种情况,你有一笔存款在银行,你在一家银行为你的账户存款,而你的妻子在另一家银行从这个账户提款,现在你有1000块在你的账户里面。你存入了1000,但是由于另一方也在对这笔存款进行操作,人家开始执行的时候只看到账户里面原来的1000元,当你的妻子提款1000元后,你妻子所在的银行就认为你的账户里面没有钱了,而你所在的银行却认为你还有2000元。
    看看下面的例子:

    class BlankSaving //储蓄账户
    {
    private static int money=10000;
    public void add(int i)
    {
    money=money+i;
    System.out.println("Husband 向银行存入了 [¥"+i+"]");
    }
    public void get(int i)
    {
    money=money-i;
    System.out.println("Wife 向银行取走了 [¥"+i+"]");
    if(money<0)
    System.out.println("余额不足!");
    }
    public int showMoney()
    {
    return money;
    }
    }


    class Operater implements Runnable
    {
    String name;
    BlankSaving bs;
    public Operater(BlankSaving b,String s)
    {
    name=s;
    bs=b;



    }
    public static void oper(String name,BlankSaving bs)
    {



    if(name.equals("husband"))
    {
    try
    {
    for(int i=0;i<10;i++)
    {
    Thread.currentThread().sleep((int)(Math.random()*300));
    bs.add(1000);
    }
    }catch(InterruptedException e){}
    }else
    {
    try
    {



    for(int i=0;i<10;i++)
    {
    Thread.currentThread().sleep((int)(Math.random()*300));
    bs.get(1000);
    }
    }catch(InterruptedException e){}
    }
    }
    public void run()
    {
    oper(name,bs);
    }
    }
    public class BankTest
    {
    public static void main(String[] args)throws InterruptedException
    {
    BlankSaving bs=new BlankSaving();
    Operater o1=new Operater(bs,"husband");
    Operater o2=new Operater(bs,"wife");
    Thread t1=new Thread(o1);
    Thread t2=new Thread(o2);
    t1.start();
    t2.start();
    Thread.currentThread().sleep(500);
    }



    }



    下面是其中一次的执行结果:



    ---------first--------------
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Husband 向银行存入了 [¥1000]


    看到了吗,这可不是正确的需求,在husband还没有结束操作的时候,wife就插了进来,这样很可能导致意外的结果。解决办法很简单,就是将对数据进行操作方法声明为synchronized,当方法被该关键字声明后,也就意味着,如果这个数据被加锁,只有一个对象得到这个数据的锁的时候该对象才能对这个数据进行操作。也就是当你存款的时候,这笔账户在其他地方是不能进行操作的,只有你存款完毕,银行管理人员将账户解锁,其他人才能对这个账户进行操作。
    修改public static void oper(String name,BlankSaving bs)为public static void oper(String name,BlankSaving bs),再看看结果:

    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Husband 向银行存入了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]
    Wife 向银行取走了 [¥1000]




    当丈夫完成操作后,妻子才开始执行操作,这样的话,对共享对象的操作就不会有问题了。
    [wait and notify]
    你可以利用这两个方法很好的控制线程的执行流程,当线程调用wait方法后,线程将被挂起,直到被另一线程唤醒(notify)或则是如果wait方法指定有时间得话,在没有被唤醒的情况下,指定时间时间过后也将自动被唤醒。但是要注意一定,被唤醒并不是指马上执行,而是从组塞状态变为可运行状态,其是否运行还要看cpu的调度。
    事例代码:

    class MyThread_1 extends Thread
    {
    Object lock;
    public MyThread_1(Object o)
    {
    lock=o;
    }
    public void run()
    {
    try
    {
    synchronized(lock)
    {
    System.out.println("Enter Thread_1 and wait");
    lock.wait();
    System.out.println("be notified");
    }
    }catch(InterruptedException e){}
    }
    }
    class MyThread_2 extends Thread
    {
    Object lock;
    public MyThread_2(Object o)
    {
    lock=o;
    }
    public void run()
    {
    synchronized(lock)
    {
    System.out.println("Enter Thread_2 and notify");
    lock.notify();
    }
    }
    }
    public class MyThread
    {
    public static void main(String[] args)
    {
    int[] in=new int[0];//notice
    MyThread_1 t1=new MyThread_1(in);
    MyThread_2 t2=new MyThread_2(in);
    t1.start();
    t2.start();
    }
    }




    执行结果如下:
    Enter Thread_1 and wait
    Enter Thread_2 and notify
    Thread_1 be notified


    可能你注意到了在使用wait and notify方法得时候我使用了synchronized块来包装这两个方法,这是由于调用这两个方法的时候线程必须获得锁,也就是上面代码中的lock[],如果你不用synchronized包装这两个方法的得话,又或则锁不一是同一把,比如在MyThread_2中synchronized(lock)改为synchronized(this),那么执行这个程序的时候将会抛出java.lang.IllegalMonitorStateException执行期异常。另外wait and notify方法是Object中的,并不在Thread这个类中。最后你可能注意到了这点:int[] in=new int[0];为什么不是创建new Object而是一个0长度的数组,那是因为在java中创建一个0长度的数组来充当锁更加高效。

    - 作者: crazybest 2006年08月25日, 星期五 00:25  回复(0) |  引用(0) 加入博采

    运用Java 5 RowSet新特性访问DB2 [转载]

    RowSet 新特性简介

      Java 5在Java Database Connectivity (JDBC)方面加强了支持,其中加入了新的包javax.sql.rowset,javax.sql.rowset.serial,javax.sql.rowset.spi。从RowSet接口继承规定了五个新的接口:

      1. CachedRowSet: CachedRowset可以不用与数据源建立长期的连接,只有当从数据库读取数据或是往数据库写入数据的时候才会与数据库建立连接,它提供了一种轻量级的访问数据库的方式,其数据均存在内存中。

      2. JdbcRowSet:对ResultSet的对象进行包装,使得可以将ResultSet对象做为一个JavaBeans ™ 组件。

      3. FilteredRowSet:继承自CachedRowSet,可以根据设置条件得到数据的子集。

      4. JoinRowSet:继承自CachedRowSet,可以将多个RowSet对象进行SQL Join语句的合并。

      5. WebRowSet:继承自CachedRowSet,可以将WebRowSet对象输出成XML格式。

      下面分别演示如何使用这五个新接口。



      实验环境

      IBM DB2 Universal 8.1
      数据库名:DemoDB
      数据库用户名:db2admin
      数据库密码:password



      CachedRowSet

      CachedRowSet可以通过调用populate(ResuletSet rs)来生成数据,一旦获得数据,CachedRowSet就可以断开与数据库的连接,直到往数据库写入数据的时候才需建立连接。

      可以使用自己扩展的或是使用Reference Implement的实现类进行访问数据库。下面的代码演示了如何根据ResultSet建立一个CachedRowSet对象,在中断与数据库连接的情况下,读取数据,并做更新,最后再获取数据库连接,将更新落实到数据库中。


    
    public static void testCachedRowSet(){
    	Connection conn = null;
    	try {
    		// 获得数据库连接
    	    conn= DriverManager.getConnection(DB2URL, DB2USER, DB2PASSWORD);
    	    Statement stmt = conn.createStatement();
    	    // 查询数据库,获得表数据
    	    ResultSet rs = stmt.executeQuery("select * from student");//$NON-NLS-1$
    	    // 根据ResultSet对象生成CachedRowSet类型的对象
    	    CachedRowSetImpl crs = new CachedRowSetImpl();
    	    crs.populate(rs);
    	    // 关闭ResultSet
    		rs.close();
    	    // 关闭数据库的连接
    	    conn.close();
    	    // 在中断与数据库连接的情况下,对CachedRowSet进行操作
    	    operateOnRowSet(crs);
    	    // 重新获取与数据库的连接
    	    conn= DriverManager.getConnection(DB2URL, DB2USER, DB2PASSWORD);
    	    // 将CachedRowSet的内容更新到数据库
    	    crs.acceptChanges(conn);
    	    // 关闭CachedRowSet
    	    crs.close();
    	    // 关闭数据库连接
    	    conn.close();
    	} catch (InstantiationException e) {
    	    System.out.println("Andrew: InstantiationException!");//$NON-NLS-1$
    	} catch (IllegalAccessException e) {
    	    System.out.println("Andrew: IllegalAccessException!");//$NON-NLS-1$
    	} catch (ClassNotFoundException e) {
    	    System.out.println("Andrew: ClassNotFoundException!");//$NON-NLS-1$
    	}catch (SQLException e) {
    	  	System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    		e.printStackTrace();
    	}	
    }
    

      其中operateOnRowSet方法遍历读取RowSet中的元素,并将id值加1。RowSet允许注册监听器,可以在光标移动,RowSet发生改变时触发。其具体代码如下:


    
    public static void operateOnRowSet(RowSet rs){
    	// 为RowSet注册监听器
    	MyRowsetListener myListener = new MyRowsetListener();
        rs.addRowSetListener(myListener);
        // 操作RowSet数据
    	try{
    		// 遍历读取数据
    		while (rs.next()) {
    			String id = rs.getString("ID");//$NON-NLS-1$
    	        String name = rs.getString("NAME");//$NON-NLS-1$
    	        System.out.println("ID="+id+",NAME="+name);//$NON-NLS-1$
    	        //在id最末位连接"1"
    	        rs.updateString(1, id+"1");
    	    }
    	}catch (SQLException e) {
    	    System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    		e.printStackTrace();
    	}
    }
    class MyRowsetListener implements RowSetListener{
    	// 光标发生移动
    	public void cursorMoved(RowSetEvent event) {
    		System.out.println("cursor moved");
    	}
    	// row发生改变
    	public void rowChanged(RowSetEvent event) {
    		System.out.println("row changed");
    	}
    	// RowSet发生改变
    	public void rowSetChanged(RowSetEvent event) {
    		System.out.println("row set changed");
    	}
    }
    public static void main(String[] args) {
    	try {
    		Class.forName(DB2DRIVER).newInstance();
    	} catch (InstantiationException e) {
    		System.out.println("Andrew: InstantiationException!");//$NON-NLS-1$
    	} catch (IllegalAccessException e) {
    		System.out.println("Andrew: IllegalAccessException!");//$NON-NLS-1$
    	} catch (ClassNotFoundException e) {
    		System.out.println("Andrew: ClassNotFoundException!");//$NON-NLS-1$
    	}
    	testCachedRowSet();
    }
    

      上面的程序的运行结果如下:


    
    cursor moved
    ID=001,NAME=zhou
    cursor moved
    ID=002,NAME=zhang
    cursor moved
    cursor moved
    cursor moved
    cursor moved
    cursor moved
    cursor moved
    row set changed
    

      并且数据库中的id更新为0011,0021。

    JdbcRowSet

      JdbcRowSet功能与ResultSet类似,与CachedRowSet不同,JdbcRowSet在操作时保持与数据库的连接。可以将与数据库连接的URL,用户名,密码以及执行的SQL语句通过setXXX形式绑定。另外,JdbcRowSet返回的结果默认是可以上下滚动和可更新的,当然这需要数据库厂商提供的JDBC Driver支持。下面的代码演示了如何通过set方法设定数据库连接参数,以及如何操作JdbcRowSet对象。


    
    public static void testJdbcRowSet() {
    	JdbcRowSetImpl jrs = new JdbcRowSetImpl();
    	try {
    		// 设置连接数据库的URL
    		jrs.setUrl(DB2URL);
    		// 设置连接数据库的用户名
    		jrs.setUsername(DB2USER);
    		// 设置连接数据库的密码
    		jrs.setPassword(DB2PASSWORD);
    		// 设置执行数据库的SQL语句
    		jrs.setCommand("select * from student");
    		// 执行操作
    		jrs.execute();
    		// 对获得的JdbcRowSet进行操作
    		operateOnRowSet(jrs);
    		// 关闭JdbcRowset
    		jrs.close();
    	} catch (SQLException e) {
    		System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    		e.printStackTrace();
    	}
    }
    
    public static void operateOnRowSet(RowSet rs) {
    	// 为RowSet注册监听器
    	MyRowsetListener myListener = new MyRowsetListener();
    	rs.addRowSetListener(myListener);
    	// 操作RowSet数据
    	try {
    		// 遍历读取数据
    		while (rs.next()) {
    			String id = rs.getString("ID");//$NON-NLS-1$
    			String name = rs.getString("NAME");//$NON-NLS-1$
    			System.out.println("ID=" + id + ",NAME=" + name);//$NON-NLS-1$
    		}
    	} catch (SQLException e) {
    		System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    		e.printStackTrace();
    	}
    }
    	

      其运行结果如下:


    
    cursor moved
    ID=0011,NAME=zhou
    cursor moved
    ID=0021,NAME=zhang
    cursor moved
    



      FilteredRowSet

      FilteredRowSet接口中规定了可以设定过滤器,其过滤接口为Predicate接口,必须实现Predicate接口中的evaluate方法。具体的代码如下:


    
    public static void testFilteredRowSet() {
    	try {
    		// 获得数据库连接
    		Connection conn = DriverManager.getConnection(DB2URL, DB2USER,
    			DB2PASSWORD);
    		Statement stmt = conn.createStatement();
    		// 查询数据库,获得表数据
    		ResultSet rs = stmt.executeQuery("select * from student");//$NON-NLS-1$
    		FilteredRowSet frs = new FilteredRowSetImpl();
    		frs.populate(rs);
    		// 设置过滤器
    		MyDBFilter filter = new MyDBFilter(11, 100);
    		frs.setFilter(filter);
    		operateOnRowSet(frs);
    		// 关闭FilteredRowSet
    		frs.close();
    		// 关闭与数据库的连接
    		conn.close();
    	} catch (SQLException e) {
    		System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    		e.printStackTrace();
    	}
    }
    
    public static void operateOnRowSet(RowSet rs) {
    	// 为RowSet注册监听器
    	System.out.println("operateOnRowSet!");//$NON-NLS-1$
    	MyRowsetListener myListener = new MyRowsetListener();
    	rs.addRowSetListener(myListener);
    	// 操作RowSet数据
    	try {
    		// 遍历读取数据
    		while (rs.next()) {
    			String id = rs.getString("ID");//$NON-NLS-1$
    			String name = rs.getString("NAME");//$NON-NLS-1$
    			System.out.println("ID=" + id + ",NAME=" + name);//$NON-NLS-1$
    		}
    	} catch (SQLException e) {
    		System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    		e.printStackTrace();
    	}
    }
    
    public static void main(String[] args) {
    	try {
    		Class.forName(DB2DRIVER).newInstance();
    	} catch (InstantiationException e) {
    		System.out.println("Andrew: InstantiationException!");//$NON-NLS-1$
    	} catch (IllegalAccessException e) {
    		System.out.println("Andrew: IllegalAccessException!");//$NON-NLS-1$
    	} catch (ClassNotFoundException e) {
    		System.out.println("Andrew: ClassNotFoundException!");//$NON-NLS-1$
    	}
    	testFilteredRowSet();
    }
    

      其中MyDBFilter实现了Predicate接口,其实现代码如下:


    
    class MyDBFilter implements Predicate {
    	private int low;
    	private int high;
    	public MyDBFilter(int low, int high) {
    		this.low = low;
    		this.high = high;
    	}
    	public boolean evaluate(RowSet rs) {
    		CachedRowSet crs=(CachedRowSet)rs;
    		//如果id在low和high之间返回真
    		try {
    			String id = (String) crs.getString("id");
    			int idValue = Integer.parseInt(id);
    			if (low < idValue && idValue < high) {
    				return true;
    			}
    		} catch (SQLException e) {
    			
    		}
    		return false;
    	}
    	public boolean evaluate(Object arg0, int arg1) throws SQLException {
    		return false;
    	}
    
    	public boolean evaluate(Object arg0, String arg1) throws SQLException {
    		return false;
    	}
    }
    

      其运行结果如下:


    
    cursor moved
    ID=0021,NAME=zhang
    cursor moved
    



      JoinRowSet

      JoinRowSet可以将多个RowSet对象进行join合并,Join的列可以通过每个RowSet通过调用setMatchColumn方法来设置。setMatchColumn方式是Joinable接口定义的方法,五种类型的RowSet规定都需要实现该接口。下面的代码演示将student表和intern表中id相同的数据进行join操作。JoinRowSet不需要保持与数据库的连接。

    public static void testJoinRowSet(){
    Connection conn = null;
    try {
    JoinRowSet jrs = new JoinRowSetImpl();
    // 获得数据库连接
    conn = DriverManager.getConnection(DB2URL, DB2USER, DB2PASSWORD);
    Statement stmt = conn.createStatement();
    // 查询数据库,获得表数据
    ResultSet rs1 = stmt.executeQuery("select id,name from student");//$NON-NLS-1$
    // 根据ResultSet对象生成CachedRowSet类型的对象
    CachedRowSetImpl crs1 = new CachedRowSetImpl();
    crs1.populate(rs1);
    crs1.setMatchColumn(1);
    // 关闭ResultSet
    jrs.addRowSet(crs1);
    rs1.close();

    // 查询数据库,获得表数据
    ResultSet rs2 = stmt.executeQuery("select id,company from intern");//$NON-NLS-1$
    // 根据ResultSet对象生成CachedRowSet类型的对象
    CachedRowSetImpl crs2 = new CachedRowSetImpl();
    crs2.populate(rs2);
    crs2.setMatchColumn(1);
    // 关闭ResultSet
    rs2.close();
    // 将Result2放入JoinRowSet中进行Join操作
    jrs.addRowSet(crs2);
    // 关闭数据库连接
    conn.close();

    while (jrs.next()) {
    String id = jrs.getString(1);
    String name = jrs.getString(2);
    String company = jrs.getString(3);
    //$NON-NLS-1$
    System.out.println("ID=" + id + ",NAME=" + name+",COMPNAY="+company);
    }
    } catch (SQLException e) {
    System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    e.printStackTrace();
    }
    }


      其输出结果为


    
    ID=001111,NAME=zhou,COMPNAY=companyA
    


      WebRowSet

      WebRowSet继承自CachedRowSet,支持XML格式的查询,更新等操作,下面的代码将WebRowSet对象输出成XML格式到文件。


    
    public static void testWebRowSet(){
    	try {
    		// 获得数据库连接
    		Connection conn = DriverManager.getConnection(DB2URL, DB2USER,
    				DB2PASSWORD);
    		Statement stmt = conn.createStatement();
    		// 查询数据库,获得表数据
    		ResultSet rs = stmt.executeQuery("select * from student");//$NON-NLS-1$
    		WebRowSetImpl wrs = new WebRowSetImpl();
    		wrs.populate(rs);
    		FileOutputStream out = new FileOutputStream("data.xml");
    		wrs.writeXml(out);
    		wrs.close();
    		// 关闭与数据库的连接
    		conn.close();
    	} catch (SQLException e) {
    		System.out.println("Andrew: SQLException!");//$NON-NLS-1$
    		e.printStackTrace();
    	} catch(IOException e){
    		System.out.println("Andrew: IOException!");//$NON-NLS-1$
    		e.printStackTrace();
    	}
    }
    

      其运行结果data.xml大致如下:


    
    
    XML文件属性格式 
    ……
    
        2
        
          1
          false
          true
          false
          0
          false
          true
          10
          ID
          ID
          ZHOUDP  
          10
          0
          STUDENT
          TEST
          12
          VARCHAR
        
        
          2
          false
          true
          false
          1
          false
          true
          50
          NAME
          NAME
          ZHOUDP  
          50
          0
          STUDENT
          TEST
          12
          VARCHAR
        
      
      
        
          0011
          zhou
        
        
          0021
          zhang
        
      
    
    

    - 作者: crazybest 2006年08月25日, 星期五 00:16  回复(0) |  引用(0) 加入博采