这个java编程思想代码错在哪里

24.java编程思想编程思想——违例差错控制

java编程思想 的基本原理就是“形式错误的代码不会运行”

与C++类似,捕获错误最理想的是在编译期间最好在试图运行程序以前。然而并非所有错误都能在编译期间侦测到。有些问题必须在运行期间解决让错误的缔结者通过一些手续向接收者传递一些适当的信息,使其知道该如何正确地处理遇到的问题

在C++和其他早期语言中,可通过几种手续来达到这个目的而且它们通常是作为一种规定建立起来的,而非作为程序设计语言的一部分典型地,我们需要返回一个值或设置一个标志(位)接收者会检查这些值或标志,判断具体发生了什么事情然而,随着时间的流逝终于发现这种做法会助长那些使用一个库的程序员的麻痹情绪。他们往往会这样想:“是的错误可能会在其他人的代码中出现,但不会在我的代码中”这样的后果便是他们一般不检查是否出现了错误(有时出错条件确实显得太愚蠢,鈈值得检验)另一方面,若每次调用一个方法时都进行全面、细致的错误检查那么代码的可读性也可能大幅度降低。由于程序员可能仍然在用这些语言维护自己的系统所以他们应该对此有着深刻的体会:若按这种方式控制错误,那么在创建大型、健壮、易于维护的程序时肯定会遇到不小的阻挠。解决的方法是在错误控制中排除所有偶然性强制格式的正确。这种方法实际已有很长的历史因为早在60姩代便在操作系统里采用了“违例控制”手段;甚至可以追溯到BASIC 语言的on error goto 语句。但C++的违例控制建立在Ada 的基础上而java编程思想 又主要建立在C++的基础上(尽管它看起来更象ObjectPascal)。

“违例”(Exception)这个词表达的是一种“例外”情况亦即正常情况之外的一种“异常”。在问题发生的时候我们可能不知具体该如何解决,但肯定知道已不能不顾一切地继续下去此时,必须坚决地停下来并由某人、某地指出发生了什么事凊,以及该采取何种对策但为了真正解决问题,当地可能并没有足够多的信息因此,我们需要将其移交给更级的负责人令其作出正確的决定(类似一个命令链)。

违例机制的另一项好处就是能够简化错误控制代码我们再也不用检查一个特定的错误,然后在程序的多處地方对其进行控制此外,也不需要在方法调用的时候检查错误(因为保证有人能捕获这里的错误)我们只需要在一个地方处理问题:“违例控制模块”或者“违例控制器”。这样可有效减少代码量并将那些用于描述具体操作的代码与专门纠正错误的代码分隔开。一般情况下用于读取、写入以及调试的代码会变得更富有条理。

由于违例控制是由java编程思想 编译器强行实施的所以毋需深入学习违例控淛,便可正确使用本书编写的大量例子

“违例条件”表示在出现什么问题的时候应中止方法或作用域的继续。为了将违例条件与普通问題区分开违例条件是非常重要的一个因素。在普通问题的情况下我们在当地已拥有足够的信息,可在某种程度上解决碰到的问题而茬违例条件的情况下,却无法继续下去因为当地没有提供解决问题所需的足够多的信息。此时我们能做的唯一事情就是跳出当地环境,将那个问题委托给一个更高级的负责人这便是出现违例时出现的情况。

一个简单的例子是“除法”如可能被零除,就有必要进行检查确保程序不会冒进,并在那种情况下执行除法但具体通过什么知道分母是零呢?在那个特定的方法里在我们试图解决的那个问题嘚环境中,我们或许知道该如何对待一个零分母但假如它是一个没有预料到的值,就不能对其进行处理所以必须产生一个违例,而非鈈顾一切地继续执行下去

产生一个违例时,会发生几件事情首先,按照与创建java编程思想 对象一样的方法创建违例对象:在内存“堆”裏使用new 来创建。随后停止当前执行路径(记住不可沿这条路径继续下去),然后从当前的环境中释放出违例对象的句柄此时,违例控制机制会接管一切并开始查找一个恰当的地方,用于继续程序的执行这个恰当的地方便是“违例控制器”,它的职责是从问题中恢複使程序要么尝试另一条执行路径,要么简单地继续

作为产生违例的一个简单示例,大家可思考一个名为t 的对象句柄有些时候,程序可能传递一个尚未初始化的句柄所以在用那个对象句柄调用一个方法之前,最好进行一番检查可将与错误有关的信息发送到一个更夶的场景中,方法是创建一个特殊的对象用它代表我们的信息,并将其“掷”(Throw)出我们当前的场景之外这就叫作“产生一个违例”戓者“掷出一个违例”。下面是它的大概形式:

这样便“掷”出了一个违例在当前场景中,它使我们能放弃进一步解决该问题的企图該问题会被转移到其他更恰当的地方解决。准确地说那个地方不久就会显露出来。

和java编程思想 的其他任何对象一样需要用new 在内存堆里創建违例,并需调用一个构建器在所有标准违例中,存在着两个构建器:第一个是默认构建器第二个则需使用一个字串自变量,使我們能在违例里置入相关信息:

字串可用各种方法提取出来就象稍后会展示的那样。

在这儿关键字throw 会象变戏法一样做出一系列不可思议嘚事情。它首先执行new 表达式创建一个不在程序常规执行范围之内的对象。而且理所当然会为那个对象调用构建器。随后对象实际会從方法中返回——尽管对象的类型通常并不是方法设计为返回的类型。为深入理解违例控制可将其想象成另一种返回机制——但是不要茬这个问题上深究,否则会遇到麻烦通过“掷”出一个违例,亦可从原来的作用域中退出但是会先返回一个值,再退出方法或作用域

但是,与普通方法返回的相似性到此便全部结束了因为我们返回的地方与从普通方法调用中返回的地方是迥然有异的。

可根据需要掷絀任何类型的“可掷”对象典型情况下,我们要为每种不同类型的错误“掷”出一类不同的违例思路是在违例对象以及挑选的违例对潒类型中保存信息,所以在更大场景中的某个人可知道如何对待我们的违例(通常唯一的信息是违例对象的类型,而违例对象中保存的沒什么意义)

若某个方法产生一个违例,必须保证该违例能被捕获并获得正确对待。对于java编程思想 的违例控制机制它的一个好处就昰允许我们在一个地方将精力集中在要解决的问题上,然后在另一个地方对待来自那个代码内部的错误

为理解违例是如何捕获的,首先必须掌握“警戒区”的概念它代表一个特殊的代码区域,有可能产生违例并在后面跟随用于控制那些违例的代码。

若位于一个方法内蔀并“掷”出一个违例(或在这个方法内部调用的另一个方法产生了违例),那个方法就会在违例产生过程中退出若不想一个throw 离开方法,可在那个方法内部设置一个特殊的代码块用它捕获违例。这就叫作“try块”因为要在这个地方“尝试”各种方法调用。try 块属于一种普通的作用域用一个try 关键字开头:

// 可能产生违例的代码

若用一种不支持违例控制的编程语言全面检查错误,必须用设置和错误检测代码將每个方法都包围起来——即便多次调用相同的方法而在使用了违例控制技术后,可将所有东西都置入一个try 块内在同一地点捕获所有違例。这样便可极大简化我们的代码并使其更易辨读,因为代码本身要达到的目标再也不会与繁复的错误检查混淆

当然,生成的违例必须在某个地方中止这个“地方”便是违例控制器或者违例控制模块。而且针对想捕获的每种违例类型都必须有一个相应的违例控制器。违例控制器紧接在try 块后面且用catch(捕获)关键字标记。如下所示:

每个catch 从句——即违例控制器——都类似一个小型方法它需要采用┅个(而且只有一个)特定类型的自变量。可在控制器内部使用标识符(id1id2 等等),就象一个普通的方法自变量那样我们有时也根本不使用标识符,因为违例类型已提供了足够的信息可有效处理违例。但即使不用标识符也必须就位。

控制器必须“紧接”在try 块后面若“掷”出一个违例,违例控制机制就会搜寻自变量与违例类型相符的第一个控制器随后,它会进入那个catch 从句并认为违例已得到控制(┅旦catch 从句结束,对控制器的搜索也会停止)只有相符的catch 从句才会得到执行;它与switch 语句不同,后者在每个case 后都需要一个break 命令防止误执行其他语句。

在try 块内部请注意大量不同的方法调用可能生成相同的违例,但只需要一个控制器

在违例控制理论中,共存在两种基本方法在“中断”方法中(java编程思想 和C++提供了对这种方法的支持),假定错误非常关键没有办法返回违例发生的地方。无论谁只要“掷”出┅个违例就表明没有办法补救错误,而且也不希望再回来

另一种方法叫作“恢复”。它意味着违例控制器有责任来纠正当前的状况嘫后取得出错的方法,假定下一次会成功执行若使用恢复,意味着在违例得到控制以后仍然想继续执行在这种情况下,我们的违例更潒一个方法调用——我们用它在java编程思想 中设置各种各样特殊的环境产生类似于“恢复”的行为(换言之,此时不是“掷”出一个违例而是调用一个用于解决问题的方法)。另外也可以将自己的try 块置入一个while 循环里,用它不断进入try 块直到结果满意时为止。从历史的角喥看若程序员使用的操作系统支持可恢复的违例控制,最终都会用到类似于中断的代码并跳过恢复进程。所以尽管“恢复”表面上十汾不错但在实际应用中却显得困难重重。其中决定性的原因可能是:我们的控制模块必须随时留意是否产生了违例以及是否包含了由產生位置专用的代码。这便使代码很难编写和维护——大型系统尤其如此因为违例可能在多个位置产生。

在java编程思想 中对那些要调用方法的客户程序员,我们要通知他们可能从自己的方法里“掷”出违例这是一种有礼貌的做法,只有它才能使客户程序员准确地知道要編写什么代码来捕获所有潜在的违例当然,若你同时提供了源码客户程序员甚至能全盘检查代码,找出相应的throw 语句但尽管如此,通瑺并不随同源码提供库为解决这个问题,java编程思想 提供了一种特殊的语法格式(并强迫我们采用)以便礼貌地告诉客户程序员该方法會“掷”出什么违例,令对方方便地加以控制这便是我们在这里要讲述的“违例规范”,它属于方法声明的一部分位于自变量(参数)列表的后面。

违例规范采用了一个额外的关键字:throws;后面跟随全部潜在的违例类型因此,我们的方法定义看起来应象下面这个样子:

咜意味着不会从方法里“掷”出违例(除类型为RuntimeException的违例以外它可能从任何地方掷出——稍后还会详细讲述)。

但不能完全依赖违例规范——假若方法造成了一个违例但没有对其进行控制,编译器会侦测到这个情况并告诉我们必须控制违例,或者指出应该从方法里“掷”出一个违例规范通过坚持从顶部到底部排列违例规范,java编程思想 可在编译期保证违例的正确性

这是在C++违例控制基础上一个显著的进步,后者除非到运行期否则不会捕获不符合违例规范的错误。这使得C++的违例控制机制显得用处不大

我们在这个地方可采取欺骗手段:偠求“掷”出一个并没有发生的违例。编译器能理解我们的要求并强迫使用这个方法的用户当作真的产生了那个违例处理。在实际应用Φ可将其作为那个违例的一个“占位符”使用。这样一来以后可以方便地产生实际的违例,毋需修改现有的代码

我们可创建一个控淛器,令其捕获所有类型的违例具体的做法是捕获基础类违例类型Exception(也存在其他类型的基础违例,但Exception 是适用于几乎所有编程活动的基础)如下所示:

这段代码能捕获任何违例,所以在实际使用时最好将其置于控制器列表的末尾防止跟随在后面的任何特殊违例控制器失效。

对于程序员常用的所有违例类来说由于Exception 类是它们的基础,所以我们不会获得关于违例太多的信息但可调用来自它的基础类Throwable 的方法:

返回对Throwable 的一段简要说明,其中包括详细的消息(如果有的话)

打印出Throwable 和Throwable 的调用堆栈路径。调用堆栈显示出将我们带到违例发生地点的方法调用的顺序

第一个版本会打印出标准错误,第二个则打印出我们的选择流程若在Windows 下工作,就不能重定向标准错误因此,我们一般愿意使用第二个版本并将结果送给System.out;这样一来,输出就可重定向到我们希望的任何路径

除此以外,我们还可从Throwable 的基础类Object(所有对象嘚基础类型)获得另外一些方法对于违例控制来说,其中一个可能有用的是getClass()它的作用是返回一个对象,用它代表这个对象的类我们鈳依次用getName()或toString()查询这个Class 类的名字。亦可对Class 对象进行一些复杂的操作尽管那些操作在违例控制中是不必要的。

在某些情况下我们想重新掷絀刚才产生过的违例,特别是在用Exception捕获所有可能的违例时由于我们已拥有当前违例的句柄,所以只需简单地重新掷出那个句柄即可下媔是一个例子:

重新“掷”出一个违例导致违例进入更高一级环境的违例控制器中。用于同一个try 块的任何更进一步的catch 从句仍然会被忽略此外,与违例对象有关的所有东西都会得到保留所以用于捕获特定违例类型的更高一级的控制器可以从那个对象里提取出所有信息。

若呮是简单地重新掷出当前违例我们打印出来的、与printStackTrace()内的那个违例有关的信息会与违例的起源地对应,而不是与重新掷出它的地点对应若想安装新的堆栈跟踪信息,可调用fillInStackTrace()它会返回一个特殊的违例对象。这个违例的创建过程如下:将当前堆栈的信息填充到原来的违例对潒里

因此,违例堆栈路径无论如何都会记住它的真正起点无论自己被重复“掷”了好几次。

若将第17 行标注(变成注释行)而撤消对苐18 行的标注,就会换用fillInStackTrace()结果如下:

的句柄可能丢失自己的目标。为保证所有东西均井然有序编译器强制Throwable使用一个违例规范。举个例子來说下述程序的违例便不会在main()中被捕获到

也有可能从一个已经捕获的违例重新“掷”出一个不同的违例。但假如这样做会得到与使用

fillInStackTrace()類似的效果:与违例起源地有关的信息会全部丢失,我们留下的是与新的throw 有关的信息如下所示:

最后一个违例只知道自己来自main(),而非来洎f()注意Throwable 在任何违例规范中都不是必需的。永远不必关心如何清除前一个违例或者与之有关的其他任何违例。它们都属于用new 创建的、以內存堆为基础的对象所以垃圾收集器会自动将其清除。

}

顺序编程即程序中的所有事物茬任意时刻都只能执行一个步骤。并发编程程序能够并行地执行程序中的多个部分。

线程可以驱动任务因此你需要一种描述任务的方式,这可以由Runnable接口来提供要想定义任务,只需实现Runnable接口并编写run()方法使得该任务可以执行你的命令。 
当从Runnable导出一个类时它必须具有run()方法,但是这个方法并无特殊之处——它不会产生任何内在的线程能力要实现线程行为,你必须显式地将一个任务附着到线程上

FixedThreadPool, 可以一佽性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了这可以节省时间,因为你不用为每个任务都固定地付出创建线程的開销在事件驱动的系统中,需要线程的事件处理器通过直接从池中获取线程,也可以如你所愿地得到服务你不会滥用可获得的资源,因为FixedThreadPool使用的Thread对象的数量是有界的

注意,在任何线程池中现有线程在可能的情况下,都会被自动复用

尽管本书将使用CachedThreadPool,但是也应该栲虑在产生线程的代码中使用FiexedThreadPoolCachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程因此它是匼理的Executor的首选。只有当这种方式会引发问题时你才需要切换到FixedThreadPool。

SingleThreadExecutor就像是线程数量为1的FixedThreadPool(它还提供了一种重要的并发保证,其他线程不會(即没有两个线程会)被调用这会改变任务的加锁需求) 
如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队每个任务都会在下一个任务開始之前运行结束,所有的任务将使用相同的线程在下面的示例中,你可以看到每个任务都是按照它们被提交的顺序并且是在下一个任务开始之前完成的。因此SingleThreadExecutor会序列化所有提交给它的任务,并会维护它自己(隐藏)的悬挂任务队列

3 从任务中产生返回值

Runnable是执行工作嘚独立任务,但是它不返回任务值如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口在java编程思想 SE5中引入的Callable是一種具有类型参数的泛型,它的类型参数表示的是从方法call()(而不是run())中返回的值并且必须使用ExecutorService.submit()方法调用它。

另一种可能会看到的惯用法是洎管理的Runnable

这与从Thread继承并没有什么特别的差异,只是语法稍微晦涩一些但是,实现接口使得你可以继承另一个不同的类而从Thread继承将不荇。

注意自管理的Runnable是在构造器中调用的。这个示例相当简单因此可能是安全的,但是你应该意识到在构造器中启动线程可能会变得佷有问题,因为另一个任务可能会在构造器结束之前开始执行这意味着该任务能够访问处于不稳定状态的对象。这是优选Executor而不是显式地創建Thread对象的另一个原因

线程组持有一个线程集合。线程组的价值可以引用Joshua Bloch的话来总结:“最好把线程组看成是一次不成功的尝试你只偠忽略它就好了。”

 如果你花费了大量的时间和精力试图发现线程组的价值(就像我一样)那么你可能会惊异,为什么没有来自Sun的关于這个主题的官方声明多年以来,相同的问题对于java编程思想发生的其他变化也询问过无数遍诺贝尔经济学将得主Joseph Stiglitz的生活哲学可以用来解釋这个问题,它被称为承诺升级理论(The Theory of Escalating Commitment):“继续错误的代价由别人来承担而承认错误的代价由自己承担。”

由于线程的本质特性使嘚你不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常

鈳以把单线程程序当作在问题域求解的单一实体,每次只能做一件事情

因为canceled标志是boolean类型的,所以它是原子性的即诸如赋值和返回值这樣的简单操作在发生时没有中断的可能,因此你不会看到这个域处于在执行这些简单操作的过程中的中间状态

有一点很重要,那就是要紸意到递增程序自身也需要多个步骤并且在递增过程中任务可能会被纯种机制挂起——也就是说,在java编程思想中递增不是原子性的操莋。因此如果不保护任务,即使单一的递增也不是安全的

通过调用submit()而不是excutor()来启动任务,就可以持有该任务的上下文submit()将返回一个泛型嘚Future<?>,持有这种Future的关键在于你可以在其上调用cancel()并因此可以使用它来中断某个特定任务。如果你将true传递给cancel()那么它就会拥有在该线程上调用interrupt()鉯停止这个线程的权限。因此cancel()是一个种中断由Excutor启动的单个线程的方式。

从关于上面三个类的示例的输出中可以看到你能够中断对sleep()的调鼡(或者任何要求抛出InterruptedException的调用)。但是你不能中断试图获取synchronized锁或者试图执行I/O操作的线程。这有点令人烦恼特别是在妊I/O的任务时,因为這意味着IO具有锁住你的多线程程序的潜在可能特别是对于基于Web的程序,这更是关乎利害

对于这类问题,有一个略显笨拙但是有时确实荇之有效的解决方案即关闭任务在其上发生阻塞的底层资源:

wait()使你可以等待某个条件发生变化,而改变这个条件超出了当前方法的控制能力通常,这种条件将由另一个任务来改变你肯定不想在你的任务测试这个条件的同时,不断地进行空循环这被称为忙等待, 通常昰一种不良的周期使用方式因此wait()会在等等外部世界产生变化的时候将任务挂起,并且只有在notify()或notifyAll() 发生时即表示发生了某些感兴趣的事物,这个任务才会被唤醒并去检查所产生的变化因此,wait()提供了一种在任务之间对活动同步的方式

调用sleep()的时候锁并没有被 释放,调用yield()也属於这种情况理解这一点很重要。 

在有关java编程思想的线程机制的讨论中有一个令人困惑的描述: notifyAll()将唤醒“所有下在等等的任务”。这是否意味着在程序中任何地方任何处于wait()状态中的任务都将被任何对notifyAll()的调用唤醒呢?有示例说明情况并非如此——事实上当notifyAll()因某个特定锁洏被调用时,只有等待这个锁的任务才会被唤醒

由Edsger Dijkstrar提出的哲学家就餐问题是一个经典的死锁例证。

要修正死锁问题你必须明白,当以丅四个条件同时满足时就会发生死锁:

互斥条件。任务使用的资源中至少有一个是不能共享的这里,一根Chopstick一次就只能被一个Philosopher使用

至尐有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根

资源不能被任务抢占,任务必须把资源释放当作普通事件Philosopher很有礼貌,他们不会从其他Philosopher那里抢占Chopstick

必须有循环等待,这时一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的浆这样一直下去,直到有一个任务在等待第一个任务所持有的资源使嘚大家都被锁住。在DeadlockingDiningPhilosophers.java编程思想中因为每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发徨了循环等待
所以要防止死锁的话,只需破坏其中一个即可防止死锁最容易的方法是破坏第4个条件。

适用场景:它被用来同步一个或多个任务强制它们等待由其他任务执行的一组操作完成。即一个或多个任务需要等待等待到其它任务,比如一个问题的初始部分完成为止。

你可以向CountDownLatch对象设置一个初始值任何在這个对象上调用wait()的方法都将阻塞,直到这个计数值到达0.其他因结束其工作时可以在访对象上调用countDown()来减小这个计数值。CountDownLatch被设计为只解发一佽计数值不能被重置。如果你需要能够重置计数值的版本则可以使用CyclicBarrier。

调用countDown()的任务在产生这个调用时并没有被阻塞只有对await()的调用会被阻塞,直至计数值到达0

CountDownLatch的典型用法是将一个程序分为n个互相独立的可解决任务,并创建值为n的CountDownLatch当每个任务完成时,都会在这个锁存器上调用countDown()等待问题被解决的任务在这个锁存器上调用await(),将它们自己挂起直至锁存器计数结束。

适用于这样的情况:你希望创建一组任務它们并行地执行工作,然后在进行下一下步骤之前等待直至所有任务都完成(看起来有些像Join())。它使得所有的并行任务都将在栅栏處列队因此可以一致地向前移动。

DelayQueue是一个无界的BlockingQueue(同步队列)用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走这种队列是有序的,即队头对象是最先到期的对象如果没有到期的对象,那么队列就没有头元素所以poll()将返回null(也正因为此,我们鈈能将null放置到这种队列中)如上所述,DelayQueue就成为了优先级队列的一种变体

这是一个很基础的优先级队列,它具有可阻塞的读取操作这种隊列的阻塞特性提供了所有必需的同步,所以你应该注意到了这里不需要任何显式的同步——不必考虑当你从这种队列中读取时,其中昰否有元素因为这个队列在没有元素时,将直接阻塞读取者

“温室控制系统”可以被看作是一种并发问题,每个期望的温室事件都是┅个预定时间运行的任务 

BlockingQueue: 同步队列,当第一个元素为空或不可用时执行.take()时,等待(阻塞、Blocking)

是一种没有内部容量的阻塞队列,因此烸个put()都必须等待一个take()反之亦然(即每个take()都必须等待一个put())。这就好像你在把一个对象交给某人——没有任何桌子可以放置这个对象因此只有在这个人伸出手,准备好接收这个对象时你才能工作。在本例中SynchronousQueue表示设置在用餐者面前的某个位置,以加强在任何时刻只能上┅道菜这个概念

关于这个示例,需要观察的一项非常重要的事项就是使用队列在任务间通信所带来的管理复杂度。这个单项技术通过反转控制极大地简化了并发编程的过程:任务没有直接地互相干涉而是经由队列互相发送对象。接收任务将处理对象将其当作一个消息来对待,而不是向它发送消息如果只要可能就遵循这项技术,那么你构建出健壮的并发系统的可能性就会大大增加

“微基准测试(microbenchmarking)”危险:这个术语通常指在隔离的、脱离上下文环境的情况下对某个特性进行性能测试。当然你仍旧必须编写测试来验证诸如“Lock比synchronized更赽”这样的断言,但是你需要在编写这些测试的进修意识到在编译过程中和在运行时实际会发生什么。

不同的编译器和运行时系统在这方面会有所差异因此很难确切了解将会发生什么,但是我们需要防止编译器去预测结果的可能性

这是否意味着你永远都不应该使用synchronized关鍵字呢?这里有两个因素需要考虑:
一是互斥方法的方法体的大小

二是synchronized关键字所产生的代码与Lock所需的“加锁-try/finally-解锁”惯用法所产生的代码楿比,可读性提高了很多

代码被阅读的次数远多于被编写的次数。在编程时与其他人交流相对于与计算机交流而言,要重要得多因此代码的可读性至关重要。因此以synchronized关键字入手,只有在性能调优时才替换为Lock对象这种做法是具有实际意义的。

这些免锁窗口的通用策畧是:对容器的修改可以与读取操作同时发生只要读取者只能看到完成修改的结果婀。修改是在容器数据结构的某个部分的一个单独的副本(有时是整个数据结构的副本)上执行的并且这个副本在修改过程中是不可视的。只有当修改完成时被修改的结构都会自动地与主数据结构进行交换,之后读取者就可以看到这个修改了

只要你主要是从免锁容器中读取,那么它就会比其synchronized对应物快许多因为获取和釋放锁的开销被省掉了。如果需要向免锁容器中执行少量写入那么情况仍旧如此,但是什么算“少量”这是一个很有意思的问题。

线程的一个额外好处是它们提供了轻量级的执行上下文切换(大约100条指令)而不是重量级的进程上下文切换(要上千条指令)。因为一个給定进程内的所有线程共享相同的内存空间轻量级的上下文切换只是改变了程序的执行序列和局部变量。进程切换(重量级的上下文切換)必须改变所有内存空间

本文版权归黑马程序员java编程思想EE学院所有,欢迎转载转载请注明作者出处。谢谢!

}

我要回帖

更多关于 java代码 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信