Evan's Blog

Java, software development and others.

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  28 随笔 :: 0 文章 :: 73 评论 :: 0 Trackbacks

2006年3月5日 #

最近一个项目需要考虑多语言的用户,自然就想采用utf-8编码,所有JSP页面都修改完毕,一切运行正常,由于一直使用FF来检测浏览器兼容性,今天不知怎的决定用IE来跑一下,发现所有表单提交的页面都提示JS错误,查了一下,发现是一个验证身份证的方法中有一个数组的初始化中保存了各个省市的名字,类似于:var area=['上海','北京']之类的形式。一般情况下,JS错误我都是在FF中用FireBug调试的,可偏偏在FF中一切正常,这下就傻眼了,注释掉这个语句自然没问题,可怎么也想不出这么正常的一个赋值怎么会弄出个浏览器兼容的问题出来。折腾了一个下午,突然想到,是不是编码的问题啊,将这个JS用utf-8编码另存,一切ok。

另外,采用utf-8编码之后,用WinMerge比较文件时,就会出来Information lost to encoding errors:之类的提示,心想,这个指定个编码就可以了吧。在编辑=>选项中,果然看到代码页的指定,如是选择指定代码页,输入utf-8,点击确定,被提示请输入一个整数,估计这时候大多数人和我一样傻眼,utf-8的代码页是多少?好在有Google,调整不同的关键字,终于找到清炒苦瓜的一篇文章中提到utf-8的代码页是65001,并且也是为了解决WinMerge的乱码问题,可是改完之后,并没有起作用,这次没有去怀疑这个代码页是不是正确,再仔细看一下那篇文章,嗯,人家明明说用的是2.6.8嘛,检查一下自己的版本,2.6.0,是不是版本问题呢?来到WinMerge的网站,发现竟然已经是2.6.12了,于是下载最新版本,然后将代码页改成65001,嗯,这下所有的中文注释都乖乖出来了。
posted @ 2008-01-05 21:41 Evan 阅读(2818) | 评论 (1)编辑 收藏

     摘要: Apache反向代理设置  阅读全文
posted @ 2008-01-04 21:13 Evan 阅读(5930) | 评论 (2)编辑 收藏

     摘要: 这个Blog,就象我久未清理的书桌,已经落了厚厚的一层灰尘,在这个新年的第一天,决定用这样一个够噱头的标题,以一篇够水的日志,来告诉自己,已经是2008年了......  阅读全文
posted @ 2008-01-01 13:22 Evan 阅读(306) | 评论 (0)编辑 收藏

     摘要: 不要假装我们是一个文明古国了,传统早已割裂,我们是个无根的民族,精神一片荒芜,伪造出的传统只加剧了我们的虚伪,凸显了我们的空洞与脆弱。  阅读全文
posted @ 2007-08-31 21:28 Evan 阅读(211) | 评论 (0)编辑 收藏

     摘要: 你是不是从svn 1.2甚至更早的版本就开始用Subversion了?是不是在用svnserve做服务器?是不是很认真的读了svn自带的文档?那么,很有可能,你也象我一样,被它小小的忽悠了一把:)。  阅读全文
posted @ 2007-04-01 14:33 Evan 阅读(3253) | 评论 (1)编辑 收藏

     摘要: 在网上兜到一篇文章,从讲述一个译者和一家出版社之间的纠纷,引申出了一些待人处世的道理,主要是关于一个人碰到不公平的事情的时候,应该怎样办,觉得颇有道理。什么样的人才是有力量的人呢,也许是那些能合理处理不合理的事情,甚至能让不合理的事情最终转化为多赢局面的人吧。人生要真能达到这样一种境界,该是怎样一副海阔凭鱼跃(管它波涛汹涌还是风平浪净),天高任鸟飞(管它乌云闭日还是阳光普照)的从容画面。  阅读全文
posted @ 2006-12-30 19:48 Evan 阅读(1059) | 评论 (1)编辑 收藏

好久没有更新Blog了,看到还有人在关注这个Blog,很是惭愧。发现还是有人在使用Polarion的SVN Web Client,并且碰到了一些小问题,尽管我工作中几乎不用这个客户端,但当初安装的版本依然还在,所以决定稍微研究一下,但愿能给喜好这款软件的人一些帮助。

1. 为什么老是提示“Your credentials are not correct. Please check them and try again. ”?
这个多半是svn初学者常碰到的问题,尤其在使用HTTP协议的时候。一开始大家都用svnserve来做svn的服务器,自然配置的是conf中的passwd。但如果采用HTTP协议的话,就得使用Basci HTTP Authentication了,需要用Apache提供的htpasswd来管理用户和口令。这个的配置在svn自带的帮助文件中第6章“httpd, the Apache HTTP Server”一节中有比较详细的解释。但如果你没有通篇看完,在Apache中没有加上Require valid-user指令的话,那是允许匿名操作的。我想,你不愿意留下这样的安全漏洞吧。

2. 怎样配置多个repository
这个也是实际中需要的,当然,在它的readme中其实是说得很清楚的。但我们有些同志就是喜欢拿来就试,尤其是在有些类似于我这种其实语焉不详的文章时,更是就喜欢照葫芦画瓢,而不去看最权威最原始的英文文档了。要配置多个repository,以HTTP协议为例,在web.xml中要删掉RepositoryUrl、Username、Password这3个参数的设置,然后加上ParentRepositoryDirectory参数,值自然是指向svn仓库的父目录了,比如http://localhost/svn/,这个东西又是哪里来的呢?自然需要在Apache中配置,用SVNParentPath来指定svn仓库的父目录,Apache会自动解析其下所有的仓库的。这里要注意一下AuthzSVNAccessFile授权文件的写法,这里将配置所有仓库的存取权限,对于每个仓库,需要用[仓库名:/module]的方式来配置。

3. 怎样使用svn协议
前面我一直用http协议做例子,实在是因为我在其2.5.0下没有配置出来过svn协议:(。这次去其网站下载了个最新的nightly版本,发现其已经能够支持http, svn, svn+ssh, ssl和proxy等6种协议了。看看其代码结构,好象也发生了很大的变化,估计应该有比较大的改进。于是,用这个版本试了一把,呵呵,轻而易举就把svn协议给连通了,包括多仓库的情况。并且其还改进了原来设置父目录地址时一定要在最后添加/的要求,估计原来在这个地方卡壳的朋友也不少吧:)。不过,新版本还是不支持中文文件名,看我以前的帖子自己改吧。

Important: 由于svn webclient采用的javasvn(现已更名为svnkit)版本较低,用svn协议在提交老的文件时会失败,但添加新的文件时没有问题,所以,大家就不要再尝试svn协议了。如果不采用SVN协议,则其官方发布的版本就没什么问题了,已经有网友重新打包了一个解决了中文文件名的版本,到这里下载。(Updated: 2007.1.20)
posted @ 2006-12-27 13:04 Evan 阅读(3613) | 评论 (2)编辑 收藏

     摘要: 使用Jarkata FileUpload最新版本解决SVNWebClient提交中文文件名或注释时出错的问题。  阅读全文
posted @ 2006-05-14 20:11 Evan 阅读(4783) | 评论 (11)编辑 收藏

     摘要: 在我的“[推荐]两款好用的SVN Web Client”一文中曾经提及我所看好的sventon中文支持不好的问题,于是在其论坛中提了个问题,今天收到他们的邮件,称在其最新的代码中已经解决了中文的问题,今天下载更新后,发现的确解决了这个问题,呵呵,更新速度挺快的。  阅读全文
posted @ 2006-04-12 15:41 Evan 阅读(1617) | 评论 (3)编辑 收藏

     摘要:   Everything is meaningless...假设你突然死掉,世界将会怎样?世界将一样绚丽,地球转的一样快,太阳系每天在宇宙中换一个位置。大海还是大海,波涛还是波涛,一样的花开花落,潮起潮落。...你是多么可怜的小虫子,在活着的短暂岁月里,在最美好的青春里,都不曾快乐过,用尽心力去聚集一大堆外在和心灵没有关系的小东西,只是出于对未来的没有信心,小小的心灵在接近熄灭的一天还在发出那个愚蠢的声音,让你忙碌,让你忧虑的声音:我要,我还要。天底下充满了这样的小虫子,当一个离开了,又有一个来了,做着同样的事情,汹涌着同样的小小念头,受着同样的煎熬。于是上帝要感慨了:虚空的虚空,凡事都是虚空。已有的事,后必再有;已行的事,后必再行。日光之下,并无新事。
  ...
  不要忧虑“不要为明天忧虑,天上的飞鸟,不耕种也不收获,上天尚且要养活它,田野里的百合花,从不忧虑它能不能开花,是不是可以开得和其它一样美,但是它就自然的开花了,开得比所罗门皇冠上的珍珠还美。你呢,忧虑什么呢? 人比飞鸟和百合花贵重多了,上帝会弃你不顾吗?”  阅读全文
posted @ 2006-04-09 22:13 Evan 阅读(972) | 评论 (0)编辑 收藏

     摘要: 推荐两款纯Java的SVN Web Client软件。其安装使用均比ViewVC要好!  阅读全文
posted @ 2006-04-06 00:30 Evan 阅读(19207) | 评论 (21)编辑 收藏

     摘要: 前几天好奇,也刚好得了点空闲,然后就想看看ViewVC对Subversion的支持程度,于是就想装个玩玩。好死不死的,在我的VMWare Workstation上刚好有个Windows Server 2003,心想,就它吧,可就这么一偷懒,折腾了我好几天,最终还是只能算将就着把它给装上了。  阅读全文
posted @ 2006-04-05 00:28 Evan 阅读(7671) | 评论 (5)编辑 收藏

常常能从galeer(嘎荔儿)的网络日志里听到一些比较好听的歌曲,并且那些点评也不错。今天听到U2 - With or Withou You,首先,这首歌还真不错;其次,不知是这首歌原来就带的呢,还是galeer自己加上了这样几句诗:

我虽然行过死荫的幽谷,
也不怕遭害;
因为你与我同在;
你的杖,你的竿,都安慰我。

--《诗篇第二十三篇第四节》

当然,一开始我不知道这是《旧约全书·诗篇》中的内容,于是就Google了一把,找到下面这篇文章《因为你与我同在》:


我去一个乡村教会讲道,在接待的弟兄家休息。看见弟兄的儿子学习很用功,很是喜爱。我请小朋友将他的语文课本借给我看。这是人教版小学四年级语文课本。当中有一篇文章吸引了我——

1989 年美国洛杉矶大地震,30万人在不到4分钟时间里受到不同程度的伤害。这其间一个小学的惨况让人心酸。多少孩子的父母因在地震中痛失爱子而哀声闻天,面对 地震后的学校废墟只能绝望离去。但一个父亲却在废墟中不断地挖掘寻找自己那才七岁的叫阿曼达的儿子。救火队长挡住他,“太危险了,随时可能发生大爆炸,请 你离开。”别的学生家长也劝说,“太晚了,没有希望了。”可这位父亲不顾这些劝阻,在废墟中不断地寻找。就在他挖了整整八个小时后,父亲听见瓦砾堆底下传 出他十分熟悉的声音,“爸爸,是你吗?”这时他发现有14个孩子在当中,因为这是在教室墙角的位置,当房顶塌下时架成大三角形,孩子们才没被砸着。“我告 诉同学们不要害怕,说只要我爸爸活着就一定会来救我,也能救大家。因为你说过,不论发生什么,你总会和我在一起!”孩子对爸爸说。“爸爸,先让我的同学出 去吧,我知道你会跟我在一起,我不怕。不论发生了什么,我知道你总会跟我在一起。”

14个孩子获救了!

我们不禁会问,如果因为大爆炸的危险而绝望地放弃,如果这个父亲和其他的家长一样绝望地离开,如果阿曼达没有“因为你与我同在”的信念,那结果又将如何?

我想说的是,绝望让生命失去,希望使生命存留。

诗人说,“因为你与我同在。”(诗篇23:3)这样的信心使诗人在人生的黑夜里依然有生命的曙光,在人生的冬天里可凭信宣告说,冬天来了,上帝的春天也不再遥远。

“因为我与你同在,要拯救你。这是耶和华说的。”(耶利米书1:8)

    阿曼达对父亲单纯的信念应一如我们对天上的父亲执著的信仰。



是啊,“绝望让生命失去,希望使生命存留”,不过呢,对于我这个不信教的人来说,大可抱着象故事中的小阿曼达相信他父亲只要活着就一定会来救他的希望一样,也不一定非要抱着耶和华会来救我们的希望的。不过,不管信什么教,基督也好,佛陀也好,其实所有经典中的智慧都是值得学习的;当然,如果你在这个浮躁的物质社会中实在找不到可以依赖的东西时,信个把教也未尝不可。
posted @ 2006-04-01 21:33 Evan 阅读(566) | 评论 (0)编辑 收藏

     摘要: SVN会取代CVS吗?这个虽然不是我们这种小程序员能决定的大事,但学学总无妨吧,这里有一些我搜集的资料。  阅读全文
posted @ 2006-03-23 23:55 Evan 阅读(3175) | 评论 (0)编辑 收藏

     摘要: 你是不是为了高的测试覆盖度而在为每个函数添加多个测试方法,甚至连getX()和setX()都不放过呢?或者,你一看到覆盖度达到100%的代码,景仰之心就开始“有如滔滔江水绵绵不绝,又有如黄河泛滥,一发不可收拾”了呢?那么,你应该读读Andrew Glover在最近的developerWorks上发表的这篇文章。  阅读全文
posted @ 2006-03-18 23:23 Evan 阅读(1189) | 评论 (0)编辑 收藏

     摘要: 《Java Threads》的第5章“Minimal Synchronization Techniques”,是这本书中到现在我认为最差的一章了,当然主要是我不喜欢JDK 1.5新推出的Atomic Class,而这一章却花了不少篇章来介绍,且牵强地改造打字程序,又语焉不详地指出这种改造的困难之处和可能带来的副作用,但却又不能从代码的实际运行中看到这种副作用,很有误导初学者的嫌疑。不过,我想,没有哪个初学者会冒风险为了用Atomic Class而将原本简单明了的算法改造得如此晦涩难懂,并且还有潜在的出错风险。所以,对于Atomic Class,我建议跳过不读,绝对没有什么损失。不过对于其中“5.1.3 Double-Checked Locking”和“5.3 Thread Local Variables”这两节倒要着重读一读,尤其是Thread Local,应该说是Java中一个比较重要的多线程工具。  阅读全文
posted @ 2006-03-11 23:11 Evan 阅读(1641) | 评论 (0)编辑 收藏

     摘要: 快来看“洋本山”怎样忽悠一个只想买一把锤子的人最后买了一个工具工厂的建造工厂的通用建造工厂。很别扭是吧,但如果你是个开发Web应用的Java程序员,你也许已经或者正在被忽悠。  阅读全文
posted @ 2006-03-11 17:04 Evan 阅读(5739) | 评论 (23)编辑 收藏

看到《Java Threads》第5章,介绍了JDK 1.5新加的一些所谓原子类(Atomic Classes),总感觉有点为原子而原子,实际操作中,又有多少人会为了少许的性能提升而刻意去用这些别扭的操作而放弃直观的synchronize关键字或者Lock类呢?不过,这里不是想讨论这个,而是当其用Atomic Classes来改造它的打字程序后,解释用原子类只是保证类似递增、递减、赋值等操作的原子性,而不能保证其所在的方法一定是线程安全的,然后说,有可能按键事件的处理可能需要等待resetScore()处理完才能执行,而这会导致错误的评分(被当成多敲了键)。由于前几章的内容相对比较简单易懂,所以也没有很仔细的运行那些例子。这里为了验证一下,就运行了一下第4章的例子,然后发现,基本上第一次的评分总是错的。这就引起了我的注意,因为,一般情况下,如果是race condition导致的错误是很难重现的,这么明显的错误很可能是程序逻辑上的错误。仔细看了一下代码,发现在start按钮的事件处理方法里,有下面这样一段代码:
startButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
                displayCanvas.setDone(false);
                producer.setDone(false);
                startButton.setEnabled(false);
                stopButton.setEnabled(true);
                feedbackCanvas.setEnabled(true);
                feedbackCanvas.requestFocus();
                score.resetScore();
            }
        });
注意重置成绩的调用放在了最后,此时,随机生成字符的线程应该被唤醒并产生了第一个字符,然后,resetScore()将需要输入的字符又设成了-1,所以,当你第一次输入字符时,总是被认为是多击了一次键而扣1分:(。既然这样,那停止然后再启动也应该会发生这个错误啊。而事实上的确是这样。我想,这不应该看做是race condition吧,有什么样的同步技术能够避免这个问题呢?除非另外弄个标志,当成绩没有被重置前,不能产生第一个字符。当然,这是不需要的,只要将score.resetScore()放到第一句就可以了。

然后又运行了第3章的例子,发现基本上没有这个问题。难道第3章的代码是正确的?打开源代码一看,重置成绩的方法还是放在最后,那这里为什么又是正确的呢?我想,大约是第3章的例子中,每次点击start按钮,都重新创建一个线程对象的原因吧。由于创建对象和初始化线程需要一定的时间,刚好给了主线程重置成绩的机会。

不知道作者有意为之呢,还是疏忽,不过,这样的错误不能算是race condition的例子。
posted @ 2006-03-09 22:11 Evan 阅读(838) | 评论 (0)编辑 收藏

第3章主要介绍了数据的同步(Data Synchronization),这一章则主要介绍线程之间的同步方法(Thread Notification),同样包括传统的wait-and-notify方法和JDK 1.5新推出的Condition Variable。在多线程编程中,数据同步和线程同步是两个最基本也是最关键的部分。
《Java Threads》一书中通过考察打字程序中当按下start和stop按钮后,每次都创建两个新的线程的效率问题来引入线程同步的概念,当然不是线程同步的主要用处。不过,教科书归教科书,实际运用则又是另一回事。所以,通过书本学习语法,通过实践来获得运用经验。

4.1 Wait and Notify

1. 等待/唤醒类似于Solaris或POSIX中的条件变量(conditon variables),或者Windows中的事件变量(evant variable)或者信号量(signal),用于某个/多个线程暂停等待某个条件的满足,而该条件将由其它线程来设置的情况。
2. 在Java中,就像每个对象有一个锁之外,任何对象都可以提供等待/唤醒的机制。就像Java中的synchronized总是表示获得某个具体对象的锁一样,wait和notify也总是等待某个具体的对象,并由该对象唤醒;同样,获得某个对象上的锁不一定是该对象需要同步一样,等待和唤醒的条件也不一定是与之绑定的对象。
3. Java中wait-and-notify的几个方法:
void wait(): 使当前线程处于等待状态,直到其它线程调用了nofity()或者notifyAll()方法为止。
void wait(long timeout): 使当前线程处于等待状态,直到其它线程调用了nofity()或者notifyAll()方法,或者超过了指定的时间(单位为ms)为止
void wait(long timeout, int nanos):与wait(long)一样,只是在某些JVM中可以精确到奈秒。
void notify(): 唤醒一个正在等待该对象的线程。
void notifyAll(): 唤醒所有正在等待该对象的线程。
注意:任何等待和唤醒方法都必须在与之对应的对象的同步方法或同步块里调用。即:wait-and-notify必须和与之对应的synchronized关键词一起使用的。
4. wait()和sleep()的主要区别:
  1) sleep()可以在任何地方调用,而wait()需要在同步方法或同步块中调用;
  2) 进入wait()函数时,JVM会自动释放锁,而当从wait()返回即被唤醒时,又会自动获得锁;而sleep()没有这个功能,因此如果在wait()的地方用sleep()代替,则会导致相应的nofity()方法在等待时不可能被触发,因为notify()必须在相应的同步方法或同步块中,而此时这个锁却被sleep()所在的方法占用。也就是说,wait-and-notify不可能与sleep()同时使用。

4.1.1 The Wait-and-Notify Mechanism and Synchronization
1. 这一节详细的讲解了wait-and-notify机制和synchronized的关系,主要是两点:1)wait-and-notify必须和synchronized同时使用;2)wait()会自动释放和获取锁;
2. 这一节中举了一个例子用来解释可能存在当条件被不满足时也有可能被唤醒的情况:
  1) 线程T1调用一个同步方法;
  2) T1检测状态变量,发现其不满足条件;
  3) T1调用wait(),并释放锁;
  4) 线程T2调用另外一个同步方法,获得锁;
  5) 线程T3调用另外一个同步方法,由于T2获得了锁,所以处于等待状态;
  6) T2修改状态变量,使其满足条件,并调用notify()方法;
  7) T3获得锁,然后处理数据,并将状态变量又设置为不满足条件的状态;
  8) T3处理完毕返回;
  9) T1被唤醒,但实际上此时条件并不满足。
这个例子刚好印证了《Effective Java》中"Item 50: Never invoke wait outside a loop"和《Practical Java》中"实践54:针对wait()和notifyAll()使用旋锁(spin locks)"。即总是用下面这种方式来调用wait():
    synchronized(obj) {
while(<condition does not hold>)
wait();

... // Perform action appropriate to condition }
或者象《Practical Java》中一样:
    synchronized(obj) {
while(<condition does not hold>) {
try {
wait();
} catch (InterruptedException e) {}
}

... // Perform action appropriate to condition }
3. 调用wait()的线程T可能在以下几种情况被唤醒:
  1) 其它线程调用了notify(),而刚好线程T得到了通知;
  2) 其它线程调用了notifyAll();
  3) 其它线程中断了线程T;
  4) 由于JVM的原因,导致了spurious wakeup。

4.1.2 wait(), notify(), and notifyAll()
1. 正像多个线程等待同一对象上的锁,当锁释放时,无法确定哪个线程会得到那个锁一样;当有多个线程在wait()时,当另外一个线程调用nofity()的时候,也不能确定哪个线程会被唤醒; 2. 因此在《Practical Java》的"实践53:优先使用notifyAll()而非notify()"建议的一样,结合实践54,可以比较好的解决线程唤醒的问题。

4.1.3 Wait-and-Notify Mechanism with Synchronized blocks
再次强调必须在同一个对象的synchronized方法或块内调用该对象上的wait和notify方法。

4.2 Condition Variables

1. 就像上面反复强调的一样,wait-and-notify机制是与特定对象及其上的锁是绑定在一起的,锁和唤醒对象不能分开,这在某些情况下不是很方便;
2. JDK 1.5提供Condition接口来提供与其它系统几乎一致的condition variables机制;
3. Condition对象由Lock对象的newCondition()方法生成,从而允许一个锁产生多个条件变量,可以根据实际情况来等待不同条件;
4. 该书的例子没有什么特别的实际意义,但JDK 1.5文档中提供了一个例子,能充分说明使用Condition Variables使得程序更加清晰易读,也更有效率:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
具体的说明请参考JDK 1.5的文档。
5. 除了用lock和await-and-signal来代替synchronized和wait-and-notify外,其语义和机制基本一样。await()在进入前也会自动释放锁,然后再返回前重新获得锁;
6. 使用Condition Variables的原因:
  1) 如果使用Lock对象,则必须使用condition variables;
  2) 每个Lock对象可以创建多个condition variable.
posted @ 2006-03-06 22:21 Evan 阅读(815) | 评论 (0)编辑 收藏

除了C是在大学中系统学的外,C++、Java、Design Pattern等等等等都是在工作中赶鸭子上架的时候学的,虽然有边做边学的乐趣;但也有为了赶时间抓进度,只要解决了问题就好而浅尝辄止的遗憾,也时有 遗忘的现象发生。近来得了点空闲,准备好好读读几本或算经典或算时髦的书。所以,就有了这样一个Blog,希望能将学习和工作中的一些小心得给记录下来, 聊以敝帚自珍之需。
posted @ 2006-03-05 23:25 Evan 阅读(323) | 评论 (0)编辑 收藏

任何一门支持多线程的语言中,多线程是都是一个让人又爱又恨的东西。Java的多线程相对而言比其它语言要简单一点,如果不是开发框架类或者系统级的程序,也许很少会碰到要明确碰到Java的多线程API,但事实上不等于你不用注意多线程安全的问题,尤其当你在开发Web程序的时候,在类中使用了静态属性(static fields)而不仅仅是对象属性(instance fields)的时候,如果在压力测试或者提交给用户使用的时候,发生了一些不可重现的错误或者数据混乱的时候,那往往要查查这些使用了静态属性的类是否是多线程安全的了。当然,如果你专注于开发Web应用,并且很少涉及框架或核心模块的开发,那也就基本上知道synchronized的关键字的应用就可以了。这也许就是Java多线程相对其它语言中多线程要简单一点的原因。

当然,这次我打算比较深入地来了解了解一下Java多线程开发的其它一些内容,那么找一本好的书是一个比 较好的开始。关于Java多线程开发的专著比较有名的大约是《Java Threads, 3rd Edition》和《Java Thread Programming》了,前者基于JDK 1.5(这个版本对多线程进行了很大的改进)进行介绍,并且指出了与以前版本的区别;而后者出版于1999年,是基于JDK 1.2进行讲解的。所以呢,基本上采用第一本为主。同时也参考一下《Practical Java》和《Effective Java》的相关条目。

这几本书的封面如下,相关书的介绍可去Amazon查看一下:


posted @ 2006-03-05 23:25 Evan 阅读(570) | 评论 (1)编辑 收藏

J2SE 5.0在多线程上做了很大的改进,提供了更多的API,包括:
Atomic variables: A set of classes that provide threadsafe operations without synchronization
Explicit locks: Synchronization locks that can be acquired and released programmatically
Condition variables: Variables that can be the subject of a targeted notification when certain conditions exist
Queues: Collection classes that are thread-aware Synchronization primitives: New classes that perform complex types of synchronization
Thread pools: Classes that can manage a pool of threads to run certain tasks
Thread schedulers: Classes that can execute tasks at a particular point in time

在《Java Threads》一书中将其归纳为三类:
1. 对现有功能的新实现;
2. 提供了重要的多线程工具,如线程池(pool)和计划(schedule);
3. 最小化同步工具(Minimal synchronization utilities)。

这些功能的妙处我现在自然是无法体会得到,但对于JDK 5.0中提供的这些多线程工具,会不会也遭遇JDK 1.4提供的Log API的命运,因敌不过第三方工具而成为摆设呢(至少目前我还在用Log4J,且其也没有停止开发的迹象)?
posted @ 2006-03-05 23:25 Evan 阅读(385) | 评论 (0)编辑 收藏

Chapter2: Thread Creation and Management

2.1 What Is a Thread?

介绍了什么是线程,以及线程(thread, multithread)与进程(process, mutltitask)的区别。其中的一个重要区别是对共享数据的访问。进程可以共享操作系统级别的某些数据区域,如剪贴板;而线程是对程序自有的数据进行共享。进程之间对共享数据的存取方法是特殊的,因此自然能够得到程序员的注意;而线程之间对共享数据的存取与对线程自己的数据的存取方法是一样的,所以常常比较隐蔽,而被程序员忽略。其实,这也是多线程开发比较难的地方。所以,这一节最后说:"A thread, then, is a discrete task that operates on data shared with other threads.(线程就是一个在与其它线程共享的数据上进行操作的单独任务。)"

2.2 Creating a Thread

1. 有两种方法创建线程:使用Thread类或者Runnable接口
2. 示例程序竟然是编一个打字游戏,所以,这本书的门槛还是有点高的:(。当然可以从该书的站点直接下载代码

3. Thread class
package java.lang;
public class Thread implements Runnable {
public Thread( );
public Thread(Runnable target);
public Thread(ThreadGroup group, Runnable target);
public Thread(String name);
public Thread(ThreadGroup group, String name);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize);
public void start( );
public void run( );
}
Thread name: 线程名,用以打印线程信息时用,缺省为Thread-N。
Runnable target:可运行的目标。一个可运行的对象就是一串可由线程执行的指令。缺省就是在run()方法中编写的内容。
Thread group:线程组。对于大多数应用来说没什么意义,缺省在同一组。
Stack size:线程堆栈大小,与平台相关。为了使程序更具可移植性,并不推荐使用。

2.3 The Lifecycle of a Thread

 

1. Creating a Thread:创建一个线程对象,并没有开始执行一个线程
2. Starting a Thread:在调用线程对象的start()方法前,一直处于等待状态。可以通过调用isAlive()方法来获取线程是否正在运行;
3. Terminating a Thread:线程在以下情况下结束  
  1)执行完run()中的语句;
  2)抛出一个异常,包括其没有捕获的异常;
  3)其所在的程序(或者容器)调用System.exit()。
注意:一个线程结束后,就不能再次启动,该线程就变成一个普通的对象,如果试图再次调用start()方法,将抛出java.lang.IllegalThreadStateException异常。如果想多次运行,要么创建新的对象;要么就是不要结束该线程。
4. Java并没有提供可靠的方法来暂停、挂起或者重启一个线程的方法,线程本身能通过sleep()函数来暂停执行。目前依然只能确保若干毫秒级别的精度。
5. Thread Cleanup:线程终止后,线程对象依然存在。可以通过join()方法以阻塞方式(blocked)来等待某个线程终止。

2.4 Two Approches to Stop a Thread

1. Setting a Flag:在线程的执行过程中判断其它线程是否将标志置位。其缺点是有时间延迟,尤其是当进入了某些被阻塞的函数如:sleep(), wait()等;
2. 调用线程的interrupt()方法,线程的执行过程中改为判断isInterrupted()的返回值。可以解决线程进入阻塞导致的延迟,但依然解决不了超长时间计算而导致的延迟。interrupt()有两个作用:1)终止sleep()和wait(),抛出InterruptedException;2)使isInterrupted()返回true。下面这段代码将演示interrupt()的作用:

public class TestInterrupted {
public static void main(String args[]) throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread.currentThread().sleep(100); //注释掉这句有什么影响呢?
System.out.println("before interrupt");
foo.interrupt();
System.out.println("after interrupt");
} }

class Foo extends Thread {
public void run() {
try{
while(!isInterrupted()) {
System.out.println("start calculating...");
double pi = 3.1415926;
for(int i=0; i<5; i++) {
for(long j=0; j<1000000; j++) {
pi *= pi;
}
System.out.println("i="+i);
}
System.out.println("before sleep");
sleep(5000); //注释掉这句及相关的try...catch语句,又怎样呢?
System.out.println("after sleep");
}
} catch(InterruptedException ex) {
ex.printStackTrace(System.out);
}
}
}

 

2.5 The Runnable Interface

为什么有了Thread类,还要有Runnable接口呢?最主要的原因是为了解决Java不能多重继承的问题。线程继承自Thread,还是仅仅实现Runnable接口,取决于这个线程的作用。不过,如果仅仅实现Runnable接口,则在线程里不能使用Thread类的方法,比如interrupt()和isInterrupted()。当然,这个还是要取决于实际情况。

2.6 Threads and Objects

主要讲解线程与对象的关系。线程根本上还是个对象,其可以存取任何对象包括其它线程对象,当然也能被其它对象存取,只要没有因为争夺公共资源而被锁定,线程对象中的任何属性和方法都有可能同时执行。并且Java中的锁只针对对象本身,并不包括对象下的属性;而对方法同步,则等同于对对象同步。更进一步的说明还可以参考《Practical Java》中的"实践46:面对instance函数,synchronized锁定的是对象(object)而非函数(methods)或代码(code)"。下面用两段代码来说明一下:
代码1:

public class TestLock {
public static void main(String args[])
throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}

class Foo extends Thread{
protected int arrI[] = new int[10];

public void run() {
try {
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
} catch(InterruptedException ex) {}
}

public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}

public void setInt2(String from, int pos, int val)
throws InterruptedException {
synchronized(arrI){
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
}
}
结果:非线程安全,setInt()在对象上加锁,而setInt2()在属性arrI上加锁,不同的锁不能保证线程安全。可能的结果如下:
Foo:arrI[0]=0 
Main:arrI[0]=0
Main:arrI[1]=21
Main:arrI[2]=22
Foo:arrI[1]=21
Main:arrI[3]=23
Main:arrI[4]=24
Main:arrI[5]=25
Foo:arrI[2]=2
Main:arrI[6]=26
Main:arrI[7]=27
Foo:arrI[3]=3
Foo:arrI[4]=4
Main:arrI[8]=28
Main:arrI[9]=29
Foo:arrI[5]=5
Foo:arrI[6]=6
Foo:arrI[7]=7
Foo:arrI[8]=8
Foo:arrI[9]=9

代码2:
public class TestLock1 {
public static void main(String args[])
throws InterruptedException{
Foo1 foo = new Foo1();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}

class Foo1 extends Thread{
protected int arrI[] = new int[10];

public void run() {
try{
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
}catch(InterruptedException ex){}
}

public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}

public synchronized void setInt2(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
  }
}
结果:线程安全
posted @ 2006-03-05 23:25 Evan 阅读(664) | 评论 (0)编辑 收藏

Chapter 3. Data synchronization

第二章中介绍了如何创建线程对象、启动和终止线程。但多线程编程的关键在于多个线程之间数据的共享和同步,从这一章开始,将详细介绍线程之间数据的共享和同步的各种方法。

3.1 The Synchronized Keywor
1. synchronized是Java中最基本也最常用的用来编写多线程安全代码的关键字,用以保护对多线程共享的数据的操作总是完整的;
2. Atomic: 当一个操作被定义成原子操作时,意味着该操作在执行过程中不会被打断;原子操作可以由硬件保证或者通过软件来模拟;
3. Mutex Lock: 在很多多线程系统,通过互斥锁来保护的共享数据。Java中的任何一个对象都有一个与之相关的锁,当一个方法被声明成synchronized时,就表示当线程进入该方法前,必须获得相应对象的锁,在执行完毕再释放这个锁。从而保证同一时刻只有一个线程调用该对象上的被声明为synchronized的方法。注意:Java的互斥锁只能加在对象级上,获得某个对象的锁,并不能保证该对象的属性和其它非synchronized的方法是线程安全的;也不能保证受保护的方法里调用的其它对象是多线程安全的,除非任何调用这些没有被保护的方法或者对象只通过受保护的方法进行调用。所以,编写线程安全的代码关键就在于规划方法和对象之间的调用关系,并尽量采用相同对象的锁来进行同步控制。

3.2 The Volatile Keyword
1. Scope of a Lock: 锁的作用范围即获得和释放锁之间的那段时间。
2. Java标准虽然声明存取一个非long和double变量的操作是原子操作,但由于不同虚拟机实现的差异,在多线程环境下每个线程可能会保留自己的工作拷贝,而导致变量的值产生冲突。为了避免这种情况的发生,可以有两种方法:
  1) 为变量创建声明为synchronized的setter和getter方法,然后任何调用(包括在类类部)该变量的地方都通过setter和getter方法;
  2) 采用volatile声明,确保每次存取这些属性时都从主内存中读入或者写入主内存中;
  3) volatile仅仅用于解决Java内存模式导致的问题,只能运用在对该变量只做一个单一装载或写入操作且该方法的其它操作并不依赖该变量的变化。如:在一个循环体中作为递增或递减变量时就不能使用volatile来解决线程同步的问题。
3. volatile的使用是有限的,一般而言,仅仅将其作为强制虚拟机总是从主内存读写变量的一个手段,或者某些需要其参数声明为volatile的函数。

3.3 More on Race Conditions
本节以打字游戏中显示成绩的例子来解释了可能存在的race condition。关键要注意以下几点:
1. 操作系统会随机的切换多个线程的运行,因此当多个线程调用同一个方法时,可能在任何地方被暂停而将控制权交给另外的线程;
2. 为了减少被误用的可能,总是假设方法有可能被多个线程调用;
3. 锁仅仅与某个特定实例相关,而与任何方法和类都无关,这一点当需要存取类属性或方法时要特别注意;
4. 任何时候只有一个线程能够运行某个类中一个被声明为synchronized的静态方法;一个线程只能运行某个特定实例中一个被声明为synchronized的非静态方法。

3.4 Explicit Locking
1. 学过Win32下编写多线程的朋友刚开始可能会被Java的Synchronized关键词搞糊涂。因为Java中的任何一个对象都有一个与之相关的锁,而不象在Win32下要先定义一个互斥量,然后再调用一个函数进入或者离开互斥区域。在JDK 1.5以后也开始提供这种显示声明的锁。JDK 1.5中定义了一个Lock接口和一些类,允许程序员显示的使用锁对象。
2. 在Lock接口里有两个方法:lock()和unlock()用来获取和释放锁对象,从而保证受保护的代码区域是线程安全的。
3. 使用锁对象的一个好处在于可以被保存、传递和抛弃,以在比较复杂的多线程应用中使用统一的锁。
在使用锁对象时,总是将lock()和unlock()调用包含在try/finally块中,以防止运行时异常的抛出而导致死锁的情况。

3.5 Lock Scope
利用lock()和unlock()方法,我们可以在任何地方使用它们,从一行代码到跨越多个方法和对象,这样就能根据程序设计需要来定义锁的作用(scope of lock)范围,而不是象以前局限在对象的层次上。

3.5.1 Synchronized Blocks

1. 利用synchronized关键字也能建立同步块,而不一定非得同步整个方法。
2. 同步一段代码时,需要明确的指定获取哪个对象的锁(这就类似于锁对象了),这样,就可以在多个方法或对象中共享这个锁对象。

3.6 Choosing a Locking Mechanism
使用锁对象还是同步关键字(synchronized),这个取决于开发员自己。使用synchronized比较简单,但相对而言,比较隐晦,在比较复杂的情况下(如要同时同步静态和非静态方法时),没有锁对象来得直观和统一。一般而言,synchronized简单,易于使用;但同时相对而言效率较低,且不能跨越多个方法。

3.6.1 The Lock Interface
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
void unlock();
Condition newCondition();
}
其中的tryLock()和tryLock(long, TimeUnit)将尝试获取锁对象,如果不能获取或在指定时间内不能获取,将立即返回。调用者可以根据返回值来判断是否获得了锁对象以做进一步的操作。

3.7 Nested Locks
1. 在一个同步方法内调用同一个对象上的另外的同步方法的情况,称之为嵌套锁(nested locking)。系统将自动执行嵌套的同步方法,而无须等待锁的释放。因此,有的时候,即使一些私有方法仅仅被已同步的方法调用,我们也给其加上synchronized关键字,以减少后续维护时可能产生的误导。
2. ReenterantLock类也支持嵌套锁。在ReenterantLock类维持一个计数器,只有当这个计数器为0时,才会释放锁。注意:这个特性是ReenterantLock类的特性,而不是所有实现Lock接口的类的特性。
3. 需要支持嵌套锁的一个原因是方法之间交叉调用(cross-calling)。设想对象a的方法M1调用对象b的方法N1,然后N1再调用对象a的方法M2,而M1和M2都是同步方法,如果不支持嵌套锁,则N1将在调用M2时等待M1释放锁,而M1则由于N1没有返回永远也不会释放锁,这样就产生了死锁。
4. synchronized和Lock接口并没有提供锁对象被嵌套获取的次数,但ReentrantLock则提供了这样一种机制:
public class ReentrantLock implements Lock {
public int getHoldCount();
public boolean isLocked();
public boolean isHeldByCurrentThread();
public int getQueueLength();
...
}
其中:
    1) getHoldCount()返回当前线程的获取次数,返回0并不表示该锁是可获取的,有可能没有被当前线程获得;
    2) isLocked()判断该锁对象是否被任何线程获得;
    3) isHeldByCurrentThread()判断是否由当前线程获得;
    4) getQueueLength()用来估计当前有多少线程在等待获取这个锁对象。

3.8 Deadlock
介绍了死锁的概念,并修改例子代码来演示deadlock的产生。死锁一般产生在多个线程在多个锁上同步时产生的;当然,多个线程在判断多个条件的时候,也有可能产生死锁。

3.9 Lock Fairness
1. Java里锁的获取机制依赖于底层多线程系统的实现,并不保证一个特定的顺序;
2. ReentrantLock类提供一个先进先出(first-in-first-out)的获取锁的选项(在创建锁对象时传入true值)。
文章来源:http://blog.csdn.net/evanwhj/archive/2006/03/05/616068.aspx
posted @ 2006-03-05 23:25 Evan 阅读(489) | 评论 (0)编辑 收藏

从2006年第五期的《读者》上看到下面几段言论,觉得颇有点意思,摘录如下:
互联网就像是酒,看你怎么用,如果你生性孤僻,互联网会让你更加孤僻,如果你要和别人交流,它也会很容易让你和别人交流。
——某位技术专家说
其实,按我的说法,互联网应该是个加速器,它往往会加剧你性格中不太容易控制的方面而变得益发不可收拾。当然,这只是对于12px的普通网友而言;而少数精英却能从中获得不菲的名利。

爱情之酒甜而苦。两人喝,是甘露;三人喝,是酸醋;随便喝,要中毒。
——陶行知

不是怀乡,没有乡愁;不是近乡,没有情怯;不是还乡,没有衣锦;不是林黛玉,没有眼泪。
——李敖自述访问大陆的心情
不管怎样说,李大师还是很有才气的,并且他特立独行、有仇必报的性格——不管好坏——也是颇令人钦佩的。只是毕竟岁月不饶人,李大师越来越有点叨唠的问题了。同样的一些话、一些事会在一系列相关的场合连续不断的说,不怕你耳朵不起茧。就像如果人家问他的风流趣事,他基本上都会说那个17朵玫瑰的故事一样。然后一众主持和现场观众都一定会做出钦佩至极,大呼高明状。

情如鱼水是夫妻双方最高的追求,但是我们都容易犯一个错误,即总认为自己是水,而对方是鱼。
——BBS经典语录
其实,总有一个是鱼,一个是水的。再说,也不一定是水的必定占上风呀。无水的鱼固然活不了,但聪明的鱼总会在干死前跳到另外的水中;而无鱼的水,也必会少了生机,甚而至于发臭了。

人要有三个头脑:天生的一个头脑,从书中得来的一个头脑,从生活中得来的一个头脑。
——契珂夫
人往往比较容易受第一个头脑的控制,受第三个头脑的诱惑,第二个头脑往往比较难以获得,在决策时也比较难以取得主动权。

除非你是比尔.盖茨,否则你跟破产之间仅有一场重病之隔。
——美国进行的一项调查发现,美国有半数破产者是被猛涨的医药费所拖累,主持这项调查的一位教授如此感叹。
如果这不是托,那么中国的现状似乎也不是个别的现象。但是,在中国,也许还有另外一个结局,那就是等死,即使得的不是绝症

posted @ 2006-03-05 23:25 Evan 阅读(256) | 评论 (0)编辑 收藏


播放视频文件

引用内容
我在这儿等着你回来
等着你回来
看那桃花开
把那花儿采
桃花朵朵开
暖暖的春风迎面吹
枝头鸟儿成双对
情人心花儿开
啊哟啊哟
你比花还美妙
叫我忘不了
秋又去春又来
记得我的爱
给你把花戴
尝尝家乡菜
团圆乐开怀
暖暖的春风迎面收
别把我忘怀
把那花儿拣
【听后感】对我而言,阿牛的歌,属于还能听一听的范围,至少可以让人轻松一点,开心一下。阿牛春节前去台湾宣传,上吴宗宪的节目,被吴恶搞了一把,喝掉大半扎各种调料混在一起的东东,主持和其他嘉宾在前面自娱自乐去了,他一个人在旁边默默地喝着。然后吴回过来头大吃一惊,说:“谁让你全喝掉的。”为表风范,吴表示要陪喝,可是连一口也喝不下去的。可见阿牛有多么老实天真了。估计,阿牛以后看到吴宗宪的背影就怕了,呵呵。



文章来源:http://blog.csdn.net/evanwhj/archive/2006/03/05/616108.aspx
posted @ 2006-03-05 23:25 Evan 阅读(320) | 评论 (0)编辑 收藏

播放视频文件
 

故事背景是很感人的网络小说《你的肩上有蜻蜓么》
引用内容
有一对非常恩爱的恋人,他们每天都去海边看日出,晚上去海边送夕阳,可是有一天,在一场车祸中,女孩不幸受了重伤,几天几夜都没有醒过来,男孩日夜守在床前,哭干了眼泪。

一个月过去了,女孩仍然昏睡,而男孩早已憔悴不堪了,看他仍苦苦支撑。上帝被他感动了,决定给这个执着的男孩一个例外,上帝问他:“你愿意用你自己的生命作为交换吗?”男孩毫不犹豫的回答“我愿意”上帝说:“那好吧,我可以让你的恋人很快醒来,看你要答应我 化做三年的蜻蜓。”

天亮了,南海变成了一只蜻蜓,他匆匆地飞到了医院,女孩真的醒了,她还在跟身旁的一名医生在交谈。

女孩出院了,但是她并不快乐。她四处打听男孩的下落,女孩整天不停地寻找,早已化成蜻蜓的男孩却无时无刻不围绕在的身边,只是他不会呼喊,不会拥抱,他只能默默地承受着她视而不见。夏天过去了,蜻蜓不得不离开这里,他最后一次飞落在女孩的肩上。他想用细小的嘴来亲亲她的额头,然而他弱小的身体不足以被她发现。春天来了,蜻蜓迫不及待地飞回来,然而,她那熟悉的身影旁站着高大而英俊的男人,那一刹那,蜻蜓几乎快从半空中跌落下来。蜻蜓伤心极了,他常常会看到那个男人带着自己的恋人在海边看日出,晚上又在海边看日落,而他自己除了偶然能停落在的肩上之外,什么也做不了。第三年夏天,蜻蜓已不再常常去看望自己的恋人了。她的肩被男医生拥着,脸被男医生轻轻地吻着,根本没时间去留意一只伤心的蜻蜓,更没有心情去怀念过去。三年的期限很快就要到了。最后一天,蜻蜓昔日的恋人跟那个男医生举行了婚礼。

蜻蜓悄悄地飞进了教堂,落在上帝的肩膀上,他听到下面的恋人对上帝发誓说“我愿意”他看着男医生把戒指戴到昔日恋人的手上,然后他看见他们甜蜜地亲吻。蜻蜓流下了伤心的眼泪。

上帝叹息着:“你后悔了吗?”蜻蜓擦干了眼泪:“没有!”上帝又带着一丝愉悦说:“明天你就可以变回你自己了。”蜻蜓摇摇头说:“就让我一辈子做蜻蜓吧……”

有些缘分是注定要失去的,爱一个人不一定要拥有,但是拥有一个人就一定要好好去爱他。你的肩上有蜻蜓吗?
【听后感】歌曲还没有细听,但觉得没有那篇小说来得感人,就像电影怎么拍也拍不出原著的味道;小说比较感人,但换了我,宁愿坚持等她醒来;或者让上帝给我另外一个选择,那就是双双化成蝴蝶,所以梁山伯与祝英台的故事比较符合中国人的口味。另外就是,爱人之间的爱一定要是双向的;如果三年后发现女孩不爱我了,用不着后悔,但我必然会再变成人,去找我的真爱。
posted @ 2006-03-05 23:25 Evan 阅读(1820) | 评论 (6)编辑 收藏


昨天晚上上了床,拿着遥控器随便转着台,突然看到周星驰的《喜剧之王》,又碰巧看到飘飘(张柏芝饰)去看伊天仇(周星驰饰)自导自演精武门,然后飘飘也自说自话地上台凑热闹,被伊天仇摔了个四脚朝天,然后坐在海滩边聊天。这时,他们之间有这样一段对话:
  飘飘:前面漆黑一片,什么也看不到。
  伊天仇:可是等到天亮,景色就会很美丽了。
这个对话大概就是这样子,是啊,如果你现在眼前一片漆黑,那倒真用不着怎样害怕,天也许就快亮了。怕的就是,眼前白茫茫一片,分不清到底是快天亮了呢,还是天要黑了。

接下来,飘飘就和伊天仇一夜风流,处于人生最低谷的伊天仇不知道这是爱情呢,还是仅仅一夜情,于是翻箱倒柜,将能找到的所有钱、手表之类的全部放在了飘飘的衣服上,继续装睡。而飘飘呢,也以为伊天仇仅仅是将自己当作风尘女子,也只能潇洒地走开。然而,当伊天仇追出来说:“我养你”的时候,我觉得应该是全剧最精彩的情节之一了。两个处于社会底层且又在自己最失意的时候,终于能够找到相互欣赏和珍惜的爱人,的确是比较感人的。难怪飘飘在出租车里拿出伊天仇一直看的那本怎样当喜剧演员的书时会泪流满面了。

唉,现在看片子,有的时候连这样一句两句令人欣赏的台词也没有了。

文章来源:http://blog.csdn.net/evanwhj/archive/2006/03/05/616120.aspx
posted @ 2006-03-05 23:25 Evan 阅读(361) | 评论 (0)编辑 收藏