Archive for the 'Design Pattern' Category
How to exit a Java app gracefully
标题:如何优雅的退出Java程序
本文中信号拦截代码来自下面的博客:
http://twit88.com/blog/2007/09/27/do-a-graceful-shutdown-of-your-java-application-when-ctr-c-kill/
在UNIX下,通常我们结束一个进程,最常用的方式就是使用kill –9 <PID>,这个方法最简单,但是会带来一个问题,程序可能正在执行某些操作,而这些操作不能被突然中断,否则会造成一些资源的泄露,或者数据完整性的问题。
在UNIX下,信号可以被用来解决这个问题,我们应该比较熟悉kill这个命令,通常使用的命令kill –9 中的kill其实不是‘杀死’的意思,而是‘发送’。kill –9 就是把 SIGKILL(数字代码为9)的信号发送到目标进程。在Java中,可以捕获这些信号:
Runtime.getRuntime().addShutdownHook(…);
当我们执行Ctrl+C或者kill命令的时候,信号Handler会被通知到,这个Handler在一个新的线程中(并非主线程),我们可以在这个Handler里面处理资源释放等问题,注意这里的kill没有带参数,其实等同于kill -15,如果使用kill -9就会直接干掉目标进程,资源得不到回收。具体做法可参考上面的链接。
当Java捕捉到SIGTERM信号的时候,它会执行子线程中的shutdown(); 代码,理论上我们可以把资源清理工作放在这里,我们知道,这个信号监听的线程和主线程,分别在处在两个线程中,这里的代码执行完毕后,它并不管主线程里面的代码是否执行完毕,直接就把整个进程结束了。假设我们有一个长循环在主线程中,它可能会在循环没有执行完毕就殒命了。这个不是我们想要的,解决也很简单:在两个线程中增加同步机制就可以了。
尝试一:
设置一个标志位(notifiedMainThreadToExit = false),当收到SIGTERM信号后,在子线程中设置notifiedMainThreadToExit为true,然后sleep(Long.MAX_VALUE),主线程轮询这个标识位,一旦发现该变量为true,就退出循环,并且释放资源。
问题出现了:主线程的确退出了,可是子线程一直长眠,这个会导致整个进程都不会退出。后来我在主线程退出前,执行System.exit,问题依旧。理论上:System.exit执行后,它会结束该虚拟机中的所有线程,不知道为什么在这里行不通。
尝试二:
设置两个标识位(notifiedMainThreadToExit = false, notifiedSignalHandlerToExit = false),当子线程收到信号后,它通知主线程(set notifiedMainThreadToExit = true):“我收到SIGTERM信号了,你可以准备收工了”,然后,子线程并不马上退出,也不永久休眠,而是轮训等待主线程通知自己。当主线程接受到子线程的通知后,退出循环,然后通知子线程(set notifiedSignalHandlerToExit = true):“我已经准备好了,你也可以收工了”,这样两个线程都可以正常的退出。
结论:
尝试二是可行的,且两个线程间不会产生Race Condition,因为没有两个线程同时写的问题,所以很安全。这里可以下载例子。最后提醒,文章提到的kill,是不带参数的,不要用kill -9。
[ad]
基于文件系统的生产者和消费者问题
周末的时候和team讨论了下如何用最简单的方式,提高数据文件的单位时间传输吞吐量。下面是一个简单的应用场景:
一个目录(DIR1),有很多Producer向这个目录里面放文件,同时有很多的Consumer负责从这个目录里面消费这些文件,插入数据库或者做其他的操作,然后删除或者移走这些文件。
假设条件:
- 一个文件中转目录DIR1,这个目录位于一个网络存储上
- 一个生产者,每一秒钟向DIR1里面放一个文件
- 若干个消费者,假设有8个,其实是8个不同的服务器,都可以访问DIR1,多台服务器可以起负载均衡的作用,任何一台或者几台出问题,整个数据流不会中断
- 解析一个文件大约需要2-14秒
- 最后一点:位于网络存储上的目录DIR1,我们认为它是不会出问题的,它不是这里的问题核心
这个场景很普遍,很多公司大概都会用到,尤其是那么比较老的系统(Legacy System),下面是两种方案:
方案一
Consumer循环扫描DIR1,一旦发现有文件,循环解析这些文件,这里有8台服务器,也就是说有8个Consumer一起这样做。代码如下:
public void run() {
System.out.println("Created consumer:" + threadName);
while (true) {
File file = new File(Constant.STAGING_FOLDER);
File files[] = file.listFiles();
for (int i = 0; i < files.length; ++i) {
File f = files[i];
parse(Constant.STAGING_FOLDER + "/" + f.getName());
}
Commons.sleep();
}
}
看起来很简单,可是上面的代码效率非常的差,多个Consumer有很大的几率拿到相同的文件,当某个Consumer尝试去解析一个文件时,却发现这个文件已经被别的Consumer解析过了,并且文件也都删除或者移走了。这样浪费的很多的CPU时间。
可以用下面的方案来替代:
方案二
public void run() {
System.out.println("Created consumer:" + threadName);
while (true) {
File file = new File(Constant.STAGING_FOLDER);
File files[] = file.listFiles();
int nCapacity = files.length > Constant.CAPACITY ? Constant.CAPACITY
: files.length;
System.out.println(this.threadName + " found " + nCapacity
+ " files");
for (int i = 0; i < nCapacity; ++i) {
File f = files[i];
f.renameTo(new File(Constant.TMP_FOLDER + "/" + f.getName()));
}
for (int i = 0; i < nCapacity; ++i) {
parse(Constant.TMP_FOLDER + "/" + files[i].getName());
}
Commons.sleep();
}
}
它和方案一的不同之处在于:它每次扫描完目录后,最多只取前若干个文件,这里是10个。并且,它不急于去处理文件,而是把文件马上移动到一个临时工作目录,其他的的操作都是相同的。
对于这个方案,有个附加条件:这个临时工作目录tmp,一定要和staging目录在同一个文件系统(filesystem),这样的话,mv操作就只是修改一下inode,几乎瞬间完成。
比较(Benchmarking)
为了测试两中方案的效率差别,我写了一个模拟程序(http://googlestop.com/download/SimConsumer.7z),它有7个class:
- App.java - 程序入口
- Commons.java - 共享的函数
- Constant.java – 配置参数
- Producer.java - 生产者,每隔一秒向目录staging里丢一个文件
- AbstractConsumer.java – 抽象消费者,定义消费者的一些基本属性和行为
- Consumer1.java - 具体消费者,实现方案一
- Consumer2.java - 具体消费者,实现方案二
在App.java中,你可以指定调用Consumer1还是Consumer2。
对于前者(Consumer1),staging目录下的文件数目不停的增长,并且如log显示,有很多冲突:一个Consumer准备处理的文件已经被其他的Consumer处理完了,造成了很多无效的操作,由于消费速度更不上生产速度,DIR1被撑爆只是时间的问题。
对于后者(Consumer2),staging目录下的文件几乎马上就会被移动到tmp目录下,大部分时间,文件数都为0。而tmp目录下,在程序稳定后大概保存在20多个文件左右,保持一个动态的平衡。用这种方式,你也会看到很多冲突,但是只会发生在程序刚开始,原因是,刚开始的时候,8个线程几乎是同时去访问staging目录,势必拿到很多相同的文件,待到稳定后,就很少有冲突发生了。
这两种方案都是最基本的,没有借助于第三方工具完成的,成本是最低的,其实还有一些其他的方案,可能会借助一些服务来实现,比如消息分发、数据库等。有时间的话,我继续补充。
[ad]
建立自己的消息循环
在MFC程序中使用消息机制的时候,常常会在程序即将完成的时候发现,在某个窗口中加入了太多的消息处理宏,并且这些消息的Handler和UI界面的方法混合在一起,待到项目规模大的时候,会导致后续的维护越来越困难。我们希望将UI的动作和业务处理的动作隔离开,互不影响,我们可以模仿MFC的消息流转机制,就本文来说,是Command的流转(Command Routing),大家知道,所有的从CCmdTarget派生的类都可以支持消息处理,比如CDocument, CView, CDocTemplate等。Framework在内部使用了Chain of Responsiblity。消息会在一个链条上找到自己的执行的地方,我们可以把不同的业务逻辑分割为各个不同的环,我们可以在修改一个环的情况下,而不去影响其他的环。附件中的例子只是简单的发送一个WM_COMMAND消息,消息的ID,表示了不同的动作。此方法适合发送消息时候,参数很少或者不用参数的情况。
示例代码的简单说明:
- 业务处理类需要从CCmdTarget派生
- 在每个业务处理类里面加入适当的消息映射(注意更新MyCommand.h中的消息的宏)
- 在CCmdManager类的BuildCmdTarget()中加入类的初始化
- 在主窗口的OnCmdMsg中调用 CCmdManager的CallOnCmdMsg方法
P.S.
这里写的只是MFC消息机制最粗糙的改造,由于时间的关系,这里不做过多的介绍
示例代码,点击这里下载
Here are some links about design pattern on the web
http://www.dofactory.com/Patterns/Patterns.aspx
http://home.earthlink.net/~huston2/dp/patterns.html
If you’re new to Design Pattern, the links listed above may be helpful. the first one is easy to understand. it shows you the skeleton of every pattern and illustatues the details in real-world code. by the way, <<Head First Design Patter>> is also an amazing book for us, it’s the best seller in many online bookshops recently.
Observer设计模式之软件语言的动态切换
随着网络的普及,软件的国际化问题,越来越值得让软件作者们多花些时间去思考。下面结合Observer模式,给出一个简单的多语言切换的例子。