欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    JAVA编程实践.doc

    • 资源ID:30976289       资源大小:401KB        全文页数:102页
    • 资源格式: DOC        下载积分:15金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要15金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    JAVA编程实践.doc

    Four short words sum up what has lifted most successful individuals above the crowd: a little bit more.-author-dateJAVA编程实践OMC概要设计Soda模板JAVA编程实践1 引言本培训的主要目的是帮助被培训者了解如何使用JAVA语言构造出高效的、不易出错的、可维护的程序。同传统的程序设计语言相比,JAVA语言通过语法上的精心设计,例如通过引用替换指针,已经避免了很多使用C+或者其他语言容易导致错误的地方。但是,程序设计语言本身的语法本身并不能够保证程序正确无误,我们发现,即使程序开发人员已经熟悉了JAVA语言的语法和函数库,要写出健壮的、高效的程序,也需要经过长时间的经验积累。使用同样的JAVA,要完成一个程序,可能会有10种编码方法,但是可能有7种都是低效的、笨拙的、可读性差、难以维护的编码方法。这是因为程序开发人员尽管已经掌握了语法规则和函数库,但是没有掌握正确的、高效的方式来编写代码。更为重要的是,开发人员有时候根本不知道什么是好的程序,什么是坏的程序,错误的使用一些技巧,使得程序复杂而且难以维护。我们通过在统一网管平台开发的实践,收集了一些最容易出错的编码问题,从中总结出一些编码的方式,这就是本次培训的目的。通过本次培训,我们希望能够帮助被培训者了解一些编码中最可能出现错误的地方,写出简单的、高效的程序,使得程序更不容易出错,使得其他人能够更好的理解这些程序,也使得在发生错误的时候,你能够更快的找到错误的原因,在需要进行功能增强的时候,更容易的修改他们的程序。2 异常处理2.1 为什么要使用异常对于一个大型的软件系统,特别是那些需要连续运行几个月的服务器软件,错误处理通常会消耗程序员极大的精力。一个中等水平的程序员,一般说来都应该能够正确完成基本的事件处理流程,而对于错误处理,就很难考虑周全。我们经常看到,程序员能够很快的完成一个所谓演示系统,或者原型系统,或者他报告说已经完成了全部功能。但是,真正的要得到一个健壮的、稳定的商用软件,还需要花费程序员很长的时间和精力。对于错误处理的估计不足,也是导致软件项目计划延期的重要原因。因此,有必要对错误处理加以特别的重视。在C、C+或者其他早期的语言中,通常采用返回值或者设置标志位的方式来处理错误。典型情况下,错误的发现函数设置一个错误码返回值/标志位,调用者检查这些返回值/标志位,判断发生的具体情况,并进行不同的处理流程。许多标准的C库函数也是采用这种方式来处理错误。这种方式使用了很多年,实际应用中发现了很多问题,其中最大的问题是,对于返回值的检查,不是依赖于语法来保证的,而是依赖于程序员的个人素质来保证的。程序员可以不检查这些错误的返回值/标志位,而在编译和运行期间,没有任何语法的措施来发现这一点。通常,正确的处理流程只有一个,而发生错误的机会却非常之多,程序员也很难对全部的错误情况考虑周全。一种情况下,错误非常的隐蔽,程序员可能考虑不到。另一种情况下,错误非常的低级,程序员会产生麻痹情绪,这种错误如此愚蠢,怎么可能发生呢?所以就没有进行条件检查。即使程序员非常有经验,水平很高。但是,当函数调用层次很深的时候,如果在最下层的函数中发生了一个错误,上层的每一级调用者必须层层检查这些返回值。这样导致代码非常的庞大,也难以阅读,降低了软件的可维护性。有时候,一个服务器程序中,错误处理的代码要占到50%以上。我们发现,采用这种方式开发一个大型的、稳定的、又易于维护的系统,是一件非常困难的事情。因此,JAVA中提供了异常处理机制,专门用于错误的处理。这种机制,将错误处理的方式作为程序设计语言的一部分,强制程序员进行错误处理。当发生异常的时候,程序员必须停下来,捕获该异常并进行处理,否则编译器就会报错:未捕获的异常。这样,就通过编译器保证了程序员必须处理错误。另一方面,异常处理机制使得错误处理的代码大大简化。编译器保证了一定会有一个地方来处理异常,这样,程序员不必层层检查返回值/标志位,而是只需要在“应该处理”的地方来处理错误。处理错误的代码和正常处理逻辑的代码很好的分离开,也有助于代码更加有条理易于维护。规则:使用异常处理机制来处理错误,而不是使用返回值或者标志位。2.2 基本语法异常是一个较新的语法,很多程序员,特别是原来的C/C+程序员,没有完全掌握异常的语法,因此有必要在这里复习一下语法。2.2.1 throwthrow关键字用于“抛出”一个异常。使用该语句通常存在于两种情况:一种是抛出一个新的异常,一种是抛出一个已经存在的异常。如:2.2.2 throws对于一个提供其他方法调用的方法,需要告诉调用者该方法会抛出什么异常,这样,调用者才能够有针对性地进行错误控制。因此,JAVA引入了一个关键字:throws。该关键字用于方法声明,在该关键字后跟随所有的可能抛出的异常类型。假如一个方法中调用了另一个可能抛出异常的方法,那么一般情况下,该方法要么捕获这些异常,并加以处理;要么也需要在自己的声明中throws这个异常。否则,编译器会报告一个错误。前面提到,异常处理机制和返回值/标志位处理方式的不同在于异常处理机制从语法上强制了程序员必须进行错误处理。这实际上表现为两个方面:首先,一个方法会发生什么错误,是在该方法的声明中通过throws语句告诉程序员的,而不是通过在注释中告诉程序员的。如果一个方法抛出了一个异常,又不在方法声明中写明,那么编译就无法通过。而编译器是无法控制是否在注释中写清楚返回值的含义的。其次,对于一个调用一个方法的程序员,他必须处理该异常,要么捕获,要么在继续向外抛出,否则编译也无法通过。2.2.3 try、catch & finallytry、catch、finally关键字用于捕获并处理异常。这里需要特别提到的是finally关键字。从语法上,在finally关键字作用范围之内的语句是正常和异常的处理流程中都需要执行的。一般情况下,这是指资源的释放。当申请一个资源之后,不论是否处理正确,处理完成之后,都必须释放该资源。将这些语句集中到finally语句块之中,有助于增加程序的可读性,并减少不释放资源的危险。在关于资源的培训中,将会有关于这一点的详细说明。2.3 异常分类2.3.1 java.lang.Throwable所有能够“throw”出来的对象的虚基类。实际应用中,应用不能够直接使用Throwable,也不能够直接从它继承,而应该继承它的两个子类。2.3.2 java.lang.ErrorError表明严重的错误,通常当发生了这种错误的时候,程序已经无法在运行下去,只能够中断运行退出。应用程序不应该捕获并处理Error,这些工作应该由虚拟机完成。除了非常底层的程序之外,一般应用程序不需要继承Error,或者抛出Error。在公司的JAVA编程规范中,禁止直接从Error继承(可以从Error的子类继承)。2.3.3 java.lang.ExceptionException表明普通的错误,应用程序可以在程序中捕获这些异常并进行处理,保证程序能够继续运行。应用程序自定义的异常,都是Exception的子类。我们在通常的情况下,处理的也都是这种类型的异常。2.3.4 java.lang.RuntimeExceptionRuntimeException是Exception的一种特殊子类,其特殊性在于:编译器不强制进行RuntimeException的处理。回顾2.2.2节,如果TestException是一个RuntimeException的子类,那么2.2节中的错误例子也能够通过编译器的检查。前面提到,异常处理机制的好处在于强制程序员进行异常处理。而如果使用了RuntimeException,则程序员可以完全不处理这些异常。这里JAVA为了编程的方便而提供了一些灵活性。那么RuntimeException一般应用于什么情况下呢?根据我的理解,RuntimeException用于表示这样一种异常:该异常只在调试期间产生,而在程序交付使用之后,根本不会出现的异常(好像名字正好取反了?)。导致该错误发生的原因是程序员的编程错误,而不是其他诸如通讯中断、磁盘错误、用户输入错误等。通常,一些公共函数库会抛出这类异常。看一个例子,假设有一个公共函数对于一个数组进行排序,参数为一个数组引用,如果传入的引用为一个空引用,那么该方法会抛出一个NullPointerException。这种错误会在什么时候产生呢?实际只有一种情况:调用该函数的程序写错了。也就是说,当调用程序调试正确之后,该异常永远不会被抛出。上述的程序能够通过编译器的检查,但是会发生了一个NullPoinrtException,应用程序不进行处理,则这个异常会由JVM进行处理,从而中断正常的处理流程。这正好给程序员一个强烈的提示,此种情况表示了一个编程错误。当程序员的程序调试正确之后,这种情况就永远不会发生。注意,不需要自己检查NullPointerException。如以下的代码:2.4 finally的特别说明2.4.1 什么都逃不过finally的掌心我们看以下的代码:在代码中的3种情况,无论是自己抛出一个异常,还是直接返回,或者是产生一个NullPointerException,都逃不出finally语句。在代码中发现一种情况,少数代码认为只要写finally,就必须写catch,结果导致了很多不必要的catch语句块。2.4.2 finally语句中不要抛出异常如果在finally语句中抛出异常,会掩盖真正需要抛出的异常,编程的时候特别需要注意这一点。假如在catch语句块和finally语句块中都抛出了异常,那么程序将不会抛出catch语句块中的异常,而是会抛出finally语句块中的异常。我们来看下面的例子:在以上的代码中,如果读写文件正常结束,执行到关闭文件的时候出了错,那么就会抛出异常,但是这种情况下,功能应该都完成了的,这会给客户端错误的信息。如果读写文件中发生异常,那么程序的原意是关闭文件,将读写文件中发生的异常抛出。但是如果在关闭文件中,也发生了异常,那么最终抛出的是关闭文件的异常,而不是读写文件的异常。这就会给客户端程序错误的信息。而且调用者的到这个关闭异常之后,也无法判断文件的读写是否已经正常完成。2.5 Exception的特别说明2.5.1 是否需要捕获Exception在代码中常常发现,有些人为了确保程序正确,往往写一个catch(Exception ex),用这种方法来确保程序能够继续运行。其实这是不对的,因为这样实际上意味着写程序的人并没有认真地考虑可能发生的异常情况,写一个大而宽泛的catch(Exception ex),看起来很保险,实际上可能会掩盖很多编程上的问题。因为我们在前面已经提过,实际上在Exception的所有子类中,除了RuntimeException之外,其他所有可能抛出的异常,如果你没有页:26catch,编译器都会发现并报错。而对于RuntimeException,我们认为是编程的错误,就是要让它暴露出来。当然,这也有一些例外情况,一个是在finally语句块中,因为不允许抛出异常,所以我们允许直接catch(Exception ex)。另一种情况是,为了确保程序在真正运行的时候不出问题,在线程的run方法中要捕获所有的异常进行处理,防止这个线程退出运行。因为实际上所有的代码都在线程中运行,只要控制住了这一点,就不会发生这些异常影响程序运行的现象发生。问题:出现异常的情况下,我需要释放资源,如果不捕获RuntimeException,不会出现资源不被释放的情况发生吗?2.5.2 不要在函数声明中throws Exception前面提到,不要捕获Exception,那么如果在函数的声明中写throws Exception,就强迫了使用者要catch Exception。所以,在函数声明中,一定要写清楚throws的具体的异常类。2.6 如何定义异常2.6.1 异常的层次和异常链如果高层方法调用了低层方法,在低层方法中抛出了一个异常,如果高层方法中不加处理的抛出该异常,那么对于该方法的使用者来说,可能会感到无法理解。因为高层方法的使用者为了理解这个异常,就必须了解高层方法的实现细节。假如高层方法的实现发生改变,就有可能导致抛出的异常发生变化,从而导致需要修改高层方法的客户端程序,因为需要捕获的异常发生变化了。为了避免这种情况发生,高层方法应该自己捕获低层方法的异常,将其转换成为按照高层解释的新的异常。这种方法称为异常转换。也就是说,抛出的异常语意应该和方法的语意层次相一致。例如,一个处理某项业务逻辑的方法,目前该方法实现中使用文件作数据存储,但是未来有可能改变为使用数据库做数据存储。那么该方法抛出的异常,其语意应该表明数据存储失败,而不是文件操作失败。否则,现在客户端程序捕获处理文件操作失败异常,当方法实现改为数据库时,客户端程序必须修改为捕获数据库操作失败异常。进行了异常转换之后,为了能够在调试程序的时候,能够追根溯源到异常的原始发生地,那么需要在高层异常中保留低层异常。高层异常中保留了低层异常,低层异常中又保留了更低层的异常,这种情况称为异常链。JAVA的Exception类提供了对于异常链的支持:Constructor SummaryException()           Constructs a new exception with null as its detail message.Exception(String message)           Constructs a new exception with the specified detail message.Exception(String message, Throwable cause)           Constructs a new exception with the specified detail message and cause.Exception(Throwable cause)           Constructs a new exception with the specified cause and a detail message of (cause=null ? null : cause.toString() (which typically contains the class and detail message of cause).使用了以上3、4的构造方法的时候,当打印异常堆栈的时候,会把cause也一起打印出来,如:java.lang.Exception: aaaat zq.sample.TestExceptionChain.openFile2(TestExceptionChain.java:40)at zq.sample.TestExceptionChain.main(TestExceptionChain.java:47)Caused by: java.io.FileNotFoundException: aaa (系统找不到指定的文件。)at java.io.FileInputStream.open(Native Method)at java.io.FileInputStream.<init>(FileInputStream.java:103)at java.io.FileInputStream.<init>(FileInputStream.java:66)at zq.sample.TestExceptionChain.openFile1(TestExceptionChain.java:22)at zq.sample.TestExceptionChain.openFile2(TestExceptionChain.java:37)2.6.2 我们系统的情况对于统一网管平台,我们将程序分为两类:公共函数,和应用程序。这两类程序的层次不同,抛出的异常类型所属的层次不一样。在平台中,一般只需要两层异常层次就可以了,低层异常由公共函数抛出,我们称为原始异常;高层异常由处理业务逻辑的应用程序抛出,称为应用异常。应用程序捕获原始异常,将其转换成为应用异常。公共函数提供一系列函数库,供其它程序使用。对于这一类函数,抛出的原始异常包括两类:RuntimeException。这些异常主要是由于调用者的程序书写不当造成的。由于JDK已经定义了绝大多数由编程错误导致的异常,所以写函数库的程序员一般情况下不需要自定义异常类型,只需要直接使用JAVA已经定义的异常类型即可。普通的异常。例如:处理IO的函数,由于磁盘硬件错误导致的异常。程序应当继承Exception,或者继承Exception的子类,定义一些特殊的异常类来表示。如果JDK中存在可用的异常类型,也可以直接使用。对于应用程序,推荐首先整理所有可能发生的错误类型,使用一种通用的异常类型来表示所有的错误。所有可能发生的错误类型,都通过这个异常的属性来表示。例如:通过一个错误代码和一个表示详细描述的字符串,来表示不同的错误。例如:2.7 抛出和捕获异常的时机2.7.1 理论对于函数库,在发现错误的时刻抛出异常。对于应用程序,相对复杂一些。如果应用程序能够直接检测到错误,那么直接抛出异常即可。捕获异常的时机比较复杂。有两种做法,一种使用较小的try语句块,精确定位所捕获的异常,另一种使用很长的try语句块,在最后捕获所有的异常。这两种做法各有优缺点,前者代码比较长,但是有助于精确定位,使程序员保持对于错误的敏感。后者代码比较简洁,属于偷懒的做法。我习惯的做法是:对于原始异常,要立刻检测,检测到之后,将其转换成应用异常抛出。对于应用异常,可以忽略,直到需要捕获的时候再处理。这里所谓需要捕获的时候,通常会有这么几种类型:输出到用户界面的时候。忽略该次错误,继续进行以下的处理时。其他需要采取错误处理措施的时候,如断链重连等。其他情况下,应用程序几乎不需要做任何事情,只需要在方法声明的throws关键字后增加AppException即可。有些程序员喜欢在每一个方法中都使用一个大的try语句,并在最后捕获一个通用的Exception。特别是在程序不稳定的情况下,以为这样可以增加程序的稳定性。这样,不仅丧失了异常处理机制使程序简洁的优点,也容易隐藏真正的异常发源地和产生原因。异常处理机制的一个优点就是,使得程序员可以一直考虑正常的处理流程,在发生错误的时候,只需要抛出异常即可。总之,可以把握一个原则,如果程序员捕获了一个异常,那么他一定需要对这个异常做一些处理,例如:一些异常处理措施(如一个告警池满之后,清除老的告警)、转换成应用异常抛出、在界面上输出错误提示等等。唯一的例外是,该错误可以忽略,继续进行以下的处理。这种情况下,会将其打印出来,用以提示发生了错误。那种捕获了一个异常,仅仅是将其继续向外抛出,这种做法是没有任何用处的。规则:不允许捕获异常之后,不做任何处理,仅仅将其继续外抛。如果仅将其打印,需要在注释中写明。2.7.2 我们系统的情况对于统一网管平台,应该在处理业务逻辑的Bean的方法中,只允许向外抛出应用异常。在这里,必须捕获所有的原始异常,将其转换为应用异常。2.7.3 什么时候输出调试信息同捕获异常一样,有的程序员喜欢将异常输出的到处都是,经常一个错误的发生,会在调试打印的输出中看到好几个异常的堆栈信息。这样,反而使得调试者无法正确定位异常的真正发生原因。推荐的输出原则是:对于函数库,不输出异常的调试打印信息,只需要把异常往外抛就是了。对于应用程序,谁处理该异常谁输出。注意:以上是指在正常运行之后的输出原则,如果出于程序调试阶段,则可以不受此限制。3 线程和共享控制3.1 概述为了提高程序的处理效率,我们需要进行多线程的编程。但是,多线程是一把双刃剑,一方面可以提供编程的极大灵活性,另一方面又非常容易导致错误,特别是在多线程的数据共享互斥和线程的执行顺序控制上,我们就曾经发现过JDK的一个Bug导致程序死锁。为了帮助程序员编写多线程的程序,JAVA函数库提供了多种函数和工具类,在我们看来,有一些函数是非常有害的,一不小心就会导致错误。我们这里介绍一下可以用于线程共享控制的函数,把它们分为几类:底层控制函数:包括Thread类和ThreadGroup类。低级函数:如Mutex、semephone、Condition等。高级函数:如读写锁、channel、Excecutor、Barriar等。3.2 底层控制函数关于线程控制方面最古老的函数是线程类Thread的一些方法:destroy、interrupt、jion、yield、suspend、resume以及线程的优先级和各类属性的设置等方法。使用这些函数会导致其大的危险,一般的开发人员要花很长的时间才能够掌握这些函数。而且,即使你掌握了这些函数的正确用法,在编码的时候也要仔细计算程序的逻辑,不同线程的执行顺序等等,非常容易出错。使用这些函数编程,对于脑细胞也是极大的破坏,一小段程序就要反复斟酌。类似的还有ThreadGroup类,该类也是极不可靠的,最终执行的结果可能和你设想的有十万八千里的差异。我们在程序中禁止使用这两个类,因为所有需要使用他们完成的控制,都可以通过后面介绍的高级函数来完成。3.3 低级函数低级函数包括用于数据共享保护的方法和用于线程控制的方法。在JAVA里面,提供了synchronize关键字用于共享数据保护,此外还有一些第3方的函数库提供如Mutex、semephorne、CriticalSection等类。下面分别介绍一下。3.3.1 MutexSynchronized提供了基本的共享数据保护方法。可以将Synchronized理解为一个公共类,提供两个方法,lock和unlock(或者叫做accqure和release等)。但是为什么JAVA要把这作为一个关键字而不是提供一个公共类呢?我们来看两段代码的样例: 如上,假如使用工具类,必须注意调用unlock,而且注意最好在finally语句中调用,以避免异常情况下无法释放。而通过Synchronized关键字,就可以从语法上避免可能出现的不释放的问题。Mutex提供了更精细一些的互斥控制,主要是当一个线程对共享数据区的访问结束以后,操作系统究竟让哪个等待的线程来访问。一般有:² 随机,即有操作系统随机的选择一个等待的线程来访问。这就等于synchronized关键字。² 按顺序调度。² 按线程的优先级调度。此外,Mutex还提供了当线程无法访问共享数据时候的行为控制,可以是:² 无限制的等待,等于synchronized关键字² 立即返回² 等待一段时间当需要一些精细的控制,synchronized无法满足要求的时候,可以使用Mutex。如果使用Mutex,就需要注意在finally语句中调用unlock方法。3.3.2 SemephorneSemephorne内部保存了一个stoken池,在初始化的时候有一个stoken池中的stoken数目,假如设置stoken数目为3,则可以有3个线程同时访问共享数据区,第4个就被挂起。每个线程访问共享数据库区的时候,从Semephorne的stoken池中取出一个stoken,取得就可以访问,访问完成以后将stoken放回Semephorne的stoken池中。如果初始化的设置stoken数目为1,就蜕化为Mutex。Semephorne的主要用处在于对访问数量作限制。3.3.3 CriticalSection和ConditionCriticalSection,也叫做临界区,概念和Mutex类似。Condition,条件变量,作为yield、suspend、resume等方法的升级版本,用于线程调度和控制。他的基本原语有:wait、notify和notifyAll,已经作为JAVA Object的最基本方法。虽然看起来这几个原语非常简单,但是真正用起来,非常的困难,也是问题百出。所以我们也不提倡在程序中使用Condition。3.4 高级函数高级函数包括读写锁、消息队列、Excecutor、Barriar。3.4.1 读写锁读写锁用于共享数据控制。它将对内存数据的访问分为读写两种,并遵循以下规则:² 读可以同时进行。² 读和写不能够同时进行。² 写不能够同时进行。通过读写锁,可以提高共享数据区的效率,提高同时存在大量的读的情况。也存在多种类型的读写锁,以做一些更加精细的控制,如写优先锁等。3.4.2 消息队列和Exceutor消息队列提供了产生请求和处理请求的解偶,用于处理产生和处理效率不匹配的情况。请求的生产者产生请求后,将其写到消息队列中,而消费者则侦听这个消息队列,从中取出请求来处理。绝大多数需要使用Condition的情况,实际上都可以用消息队列来处理,不仅简单的多,而且不容易出错。对于请求的处理,通常可以采取几种策略:² 单线程策略:使用一个线程处理所有的请求,适用于请求的处理有顺序要求的情况。² 每请求一个线程:对于每一个请求,都使用一个线程来处理,当该请求处理完毕之后,线程就消亡。这种策略不利于控制整个系统的线程数量,我们不提倡采取该策略。对于效率要求高,需要并行处理的情况,推荐使用下面的线程池策略。² 线程池:使用一个线程池来处理请求。当收到一个请求后,从线程池中取出一个空闲的线程来处理,处理完毕之后,将该线程回收到线程池。如果没有空闲的线程,则请求挂起/拒绝。Exceutor封装了这几种情况的线程管理,使得我们可以简单的设置策略,就可以管理线程。Barrire使用情况比较少,可以忽略,我也不懂。3.4.3 实现在统一网管平台中,集成了一个concurrent.jar,可以提供以上所有的函数。在实际应用中,我们最可能用到的是Synchronized、MessageQueue、Executor,对于这几个类的用法,都非常简单,可以后面自己学习。4 资源4.1 概述所谓资源,包括线程、内存、数据库连接、socket连接、文件句柄等。所有这些东西有一个共同特点,它们的数量是有限制的,不能够无限制的获取和使用。这通常有两种情况,某些资源,例如同时打开的文件数目、socket连接等,操作系统允许的数量很少,一个应用程序如果占用这些资源,其他应用程序就不能够正常工作,这将较快的导致系统的不正常。另外一种情况,如内存或者JAVA中的线程数量,系统允许的数量较多,应用程序占用这些资源的后果不会立即显现出来,刚开始仅仅造成系统性能的下降,它的严重影响需要一个缓慢的过程(如持续运行几天或者几个月)才能够显现。对于资源的使用,要注意两件事情:1)不要泄漏,使用完了以后要关闭。这是最基本的要求。2)资源使用要有总量控制,每个模块要对自己使用的资源有估计,整个系统要进行总量控制。对于我们的系统,主要考虑的是以下几类资源:² 数据库连接² 消息服务器连接² 对象远程引用有经验的程序员都知道,如果由于编程不正确导致资源处理不正确,那么将是调试程序的一个噩梦。同功能性错误相比,这些Bug引起的故障,常常无法重现,或者需要很长时间才能够重现。而且,在查找这些Bug的过程中,调试工具通常也起不了太大的作用。当一个系统的功能基本调通以后,资源的问题就成为一个主要的调试内容。因此,有必要从编程开始,就对这些方面的内容引起高度重视。4.2 资源的使用方式4.2.1 永久性使用永久性使用是指程序在初始化的时候申请资源,以后就一直使用,永远不释放,直到程序运行结束,由操作系统或者JVM强行释放。由于每一种资源在系统中的数量是有限制的,所以这种用法非常危险。一个模块永久占用一个资源,意味着其它模块可使用的资源就少了一个。如果有很多模块都这样做,那么各模块单独运行的时候,可能不会发生问题。但是当所有这些模块集成在一起运行的时候,就有可能出现资源不足的情况。永久性用法的另一个危险在于,多线程情况下,如果一项功能可能同时运行,程序员有时候会忘记考虑多线程的因素,而使用同一个资源,从而导致共享冲突的情况发生(如数据库连接)。对于某些资源,并不是申请之后就一直能够正确使用的,有时候程序不得不去写代码维护这个资源。例如:数据库连接在长时间不用之后,系统会自动将其释放。当通讯中断之后,消息服务器的连接需要重新建立。为了维护这些资源,经常需要了解资源的内部情况,这是普通的程序员很难考虑周全的。基于以上3个原因,我们一般情况下,不建议使用永久性方式。如果由于性能或者其他原因必须使用,则应该考虑以上3种情况,并在资源声明的注释中加以详细说明程序是如何处理这3种情况。我们再次回顾一下需要注意的3个方面:1)永久性使用要考虑资源的永久占用是否会影响整个系统。2)考虑共享冲突。3)最重要的,要自己维护资源。某些程序员认为,永久性使用,或者类似的搞一个缓存,可以提高程序的运行效率。这也是某些人坚持使用永久性方式的一个“有力”的理由。有一句名言:“过早的优化是一切麻烦的根源”。正确的方式是:只优化那些需要优化的代码。在进行程序设计的时候,除非已经知道此处将会成为性能瓶颈,否则更多地考虑程序的简单、可维护性,而不是进行复杂的优化而将其变得难以阅读维护,使得潜在的错误更多。即使需要进行资源缓存以提高效率,也应该遵循以下的原则:1) 整个系统应针对某项资源进行统一缓存,而不是每一个模块都自己做缓存处理。2) 该缓存的维护代码由这方面的专家来编写,而不是具体应用程序的编写者自己来做。3) 进行缓存和不进行缓存的情况下,应用编程者面对的接口不变。4.2.2 一次性使用一次性使用,是指每进行一次处理,程序就申请一个资源,处理完成之后立即释放。同永久性使用方式相比,这种方式处理要简单得多,首先,不必考虑多线程共享冲突的问题。因为通常情况下,每次处理只可能在一个线程中进行。其次,应用程序不必考虑资源的维护。资源发生故障,需要进行维护的概率是比较小的,一般说来,一次请求处理非常短暂,在申请成功之后立即发生故障的概率极小,几乎可以不考虑。其次,即使发生了故障,这些故障排除所需要的时间也比一次请求处理所需要的时间长的多,在请求期间基本上只能够返回一个错误。这和不进行维护的效果是一样的。所以,对于一次性使用方式,只需要注意一个方面:资源的释放。尽管这听起来简单,但是却发生了很多错误。对于资源需要释放这一点,无论怎样强调都不为过。4.2.3 资源的使用接口封装提供给应用的资源使用接口应该进行封装,而不是采用示范代码的方式。封装的好处在于:1) 减少了应用出错的机会。2) 简化了以后可能发生的修改。对于资源的封装,最好能够达到这样几个原则:首先,对于一种资源,应用只需要和一个类打交道。为了设计的灵活,或者采取各种各样的设计模式,其结果是应用开发者不得不面对相当多的类。为了完成一个简单的获取资源的功能,需要好几条语句。我个人认为,应当使用一个façade模式,提供给用户一个类,封装所有的操作。其次,资源的获取应当只调用一个函数open即可完成。如果以后发现该函数不能够满足要求,则增加其他的函数open即可。第三,资源的释放只调用一个函数free完成。举例:class DBHandle private Connection con = null;public DbHandle() public void open(String dsName) throws XxxException public void free() throws XxxException4.2.4 应用编程有了以上的基础,对于一次性使用方式,应用编程就非常简单了。DBHandle dbh = new DBHandle();try dbh.get(“”);catch() finally dbh.free();建议尽量采用以上的程序结构。如果无法采用,需要考虑两个原则,首先,对于一次性使用的方式,建议该次处理所需要的所有资源统一在处理开始的时候申请。中间调用其他类方法的时候,将资源作为参数传入。其次,资源的释放要统一在finally语句块中完成。对于统一网管平台,最好统一在处理业务逻辑的Bean方法开始申请资源,在该方法的finally语句块中释放资源。其他公共函数在设计时,都不要自己去申请资源,而是应该将资源句柄作为方法参数,要求客户端程序传入。目前对于资源仍然使用J2EE标准接口的情况下,资源的申请和释放应该使用标准的代码。这部分代码将后续提供。4.3 资源的总量控制4.3.1 效率和资源的平衡为了提高处理效率,采用多线程是最好,也是最省事的办法。实际上,线程的使用是有限度的,一般情况下,线程越多,效率越高。但是线程也有副作用,一是系统对线程的管理有开销,线程之间切换也有代价。另一方面,每多启动一个线程,就会多一些资源的消耗,多出很多对象,多出很多的内存。当线程的数量到达一定程度以后,系统在管理线程的开销,线程带来的各种资源的消耗,就会大于线程带来的好处,导致系统系统的急剧下降。可以用如下的曲线来描述:所以,线程不是越多越好。特别是从某些模块的角度来看,自己对资源的占用并不过分,启动10个线程也不多,但是我们是做一个平台,一个模块单独运行可能没有问题,集成到平台中来就可能出现问题。平台单独运行没有问题,加上应用之后就可能出现问题。所以,每一个模块都要控制自己对资源的使用,控制线程的数量。当然,具体数量是多少,我们也没有总结出合适的数据来。但是至少每一个模块要进行控制,不能够无限制的启动。4.3.2 平台的情况从我们的系统来讲,所有的代码都是4种力量驱动的:² 用户的操作驱动。² 定时器的驱动。² 网元或者其他系统上报的消息驱动。² 自己启动的线程。我们根据这4种驱动,就可以简单的计算最大可能情况下的线程数量。超出规定的限制之后,要能够“拒绝”这些请求。因为,计算机的处理能力不是无限大,当请求超出了处理能力之后,就要将这些请求拒绝掉,直到目前的请求处理完成以后,能够恢复回来。否则就会出现越忙越乱,越乱越忙的现象,最后导致了系统的崩溃。以前平台的设计中没有考虑到,导致了很多的问题:² JMS的设计。² PCS性能数据入库的问题。总之,从模块设计的角度来看,应当有一种稳定优于服务质量,“try my best”的思想,不要因为为了无限制的提高服务质量而导致系统的不稳定。如果因为这样使得整个系统无法完成任务,就要考虑升级硬件配置。从

    注意事项

    本文(JAVA编程实践.doc)为本站会员(豆****)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开