Archive for the 'Programming Language' 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]
NoSuchMethodError of BeanUtils.copyProperty(due to wrong access level)
为了节省开发时间,今天打算做一个数据集合类,可以直接将java中的ResultSet,或者其他Collection派生类的内容copy到该集合中,然后加入自定义的一些方法,比如支持直接导出Excel、CSV、KDF、Image、HTML等。这里借助Apache Commons BeanUtils的和反射(Reflection),将数据库中的一行记录保存为一个对象,然后插入数据集中,结果老是报错如下:
USING CONVERTER org.apache.commons.beanutils.converters.IntegerConverter@1befab0
java.lang.reflect.InvocationTargetException: Cannot set id
at org.apache.commons.beanutils.BeanUtilsBean.copyProperty(BeanUtilsBean.java:449)
at org.apache.commons.beanutils.BeanUtils.copyProperty(BeanUtils.java:129)… …
Caused by: java.lang.NoSuchMethodException: Property ‘id’ has no setter method
at org.apache.commons.beanutils.PropertyUtilsBean.setSimpleProperty(PropertyUtilsBean.java:1746)
at org.apache.commons.beanutils.BeanUtilsBean.copyProperty(BeanUtilsBean.java:447)
代码段如下:
private void dumpResultSet(ResultSet rs, Class clazz) throws Exception {
ResultSetMetaData metaData = (ResultSetMetaData) rs.getMetaData();
int colCnt = metaData.getColumnCount();
Field[] fields = clazz.getDeclaredFields();
while (rs.next()) {
Object newInstance = clazz.newInstance();
for (int i = 1; i <= colCnt; i++) {
try {
Object value = rs.getObject(i);
for (int j = 0; j < fields.length; j++) {
Field f = fields[j];
if (f.getName().equalsIgnoreCase(
metaData.getColumnName(i).replaceAll("_", ""))) {
log.info("f.getName:" + f.getName());
BeanUtils.copyProperty(newInstance, f.getName(),
value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
list.add(newInstance);
}
}
The root cause: public is required for class UserInfo, or else you’ll get the ‘NoSuchMethodError‘ exception, DO NOT ignore the access level
public class UserInfo {
private int id;
private String user;
private String password;
public UserInfo() {
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Eclipse一直提示:NoSuchMethodError,可是UserInfo里面明明有对应的setter方法,最后,才鬼使神差的发现,只要将UserInfo整个类声明为public就可以了。我一直都把注意力放到是否把setter和getter设置为public,却忽略了Bean的访问级别,希望碰到类似问题的朋友可以注意一下。
[ad]
A XFire error and solution
when i tried to deploy my web service with XFire, i got the following error message:
Exception in thread "main" org.codehaus.xfire.annotations.AnnotationException: Service class cannot be abstract: com.webserviceproject.xifre…
the root cause is mis-matched dependency libs, for my case, i’ve imported xfire-annotation-1.2.6.jar and xfire-annotation-1.1.1.jar at the same time. coz i used the wrong pom.xml as below:
<dependency>
<groupId>org.codehaus.xfire</groupId>
<artifactId>xfire-jaxb2</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.xfire</groupId>
<artifactId>xfire-spring</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.xfire</groupId>
<artifactId>xfire-java5</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.xfire</groupId>
<artifactId>xfire-all</artifactId>
<version>1.2.6</version>
</dependency>
see, actually i’ve imported XFire lib twice(1.1.1 & 1.2.6), that’s the reason why the Java complains, we just need the dependency in green.
good luck.
ASP.net 发布的小问题
Exception Details: System.Data.SqlClient.SqlException: Failed to update database “C:InetpubwwwrootSSRAPP_DATAASPNETDB.MDF” because the database is read-only.
上面的例子通常发生在publish一个新开发的web application的时候,解决方法很简单:即修改App_Data这个目录的Security属性,加入用户”NETWORK SERVICE”,并设置该用户可以对这个目录有Write权限。
[ad]
C# LDAP Wrapper
Before you try to run the following code, please download Novel LDAP lib:
http://forge.novell.com/modules/xfcontent/downloads.php/ldapcsharp/ldapcsharp/CsharpLDAP-v2.1.10/
using System;
using System.Collections.Generic;
using System.Text;
using Novell.Directory.Ldap;
namespace LDAPUtility
{
class LDAPUtil
{
private string ldapHost = "ssuzdc3";
private int ldapPort = 389;
LdapConnection ldapConn = null;
private void Connect()
{
try
{
// Creating an LdapConnection instance
ldapConn = new LdapConnection();
// Connect function will create a socket connection to the server
ldapConn.Connect(ldapHost, ldapPort);
// Bind function with null user dn and password value will perform anonymous bind
// to LDAP server
ldapConn.Bind(null, null);
}
catch (Exception e)
{
// failed to connect to server
}
}
private void Disconnect()
{
ldapConn.Disconnect();
ldapConn = null;
}
public string GetSupervisor(string id)
{
Connect();
string boss = "";
try
{
LdapSearchResults lsc = ldapConn.Search("OU=Users,OU=Suzhou,DC=charry,DC=org",
LdapConnection.SCOPE_ONE,
"sAMAccountName=" + id,
null,
false);
while (lsc.hasMore())
{
LdapEntry nextEntry = null;
try
{
nextEntry = lsc.next();
}
catch (LdapException e)
{
// Exception is thrown, go for next entry
continue;
}
LdapAttribute attribute = nextEntry.getAttribute("manager");
boss = attribute.StringValue;
}
}
catch (Exception e)
{
// exception
}
Disconnect();
return GetAMAcountName(boss);
}
private string GetFullName(string id)
{
// CN=Wang, Charry,OU=Users,OU=Suzhou,DC=charry,DC=org
int end = id.IndexOf("OU=");
id = id.Substring(3, end - 4);
id = id.Replace("\", "");
return id;
}
// convert distinguished name to AMAcountName
public string GetAMAcountName(string id)
{
id = GetFullName(id);
Connect();
string tmp = "";
try
{
LdapSearchResults lsc = ldapConn.Search("OU=Users,OU=Suzhou,DC=charry,DC=org",
LdapConnection.SCOPE_ONE,
"displayName=" + id,
null,
false);
while (lsc.hasMore())
{
LdapEntry nextEntry = null;
try
{
nextEntry = lsc.next();
}
catch (LdapException e)
{
// Exception is thrown, go for next entry
continue;
}
LdapAttribute attribute = nextEntry.getAttribute("sAMAccountName");
tmp = attribute.StringValue;
}
}
catch (Exception e)
{
// exception
}
Disconnect();
return tmp;
}
public string GetDisplayName(string id)
{
Connect();
string name = "";
// get fullname
try
{
LdapSearchResults lsc = ldapConn.Search("OU=Users,OU=Suzhou,DC=charry,DC=org",
LdapConnection.SCOPE_ONE,
"sAMAccountName=" + id,
null,
false);
while (lsc.hasMore())
{
LdapEntry nextEntry = null;
try
{
nextEntry = lsc.next();
}
catch (LdapException e)
{
// Exception is thrown, go for next entry
continue;
}
LdapAttribute attribute = nextEntry.getAttribute("displayName");
name = attribute.StringValue;
}
}
catch (Exception e)
{
// exception
}
Disconnect();
return name;
}
public string GetEmail(string id)
{
Connect();
string email = "";
try
{
LdapSearchResults lsc = ldapConn.Search("OU=Users,OU=Suzhou,DC=charry,DC=org",
LdapConnection.SCOPE_ONE,
"sAMAccountName=" + id,
null,
false);
while (lsc.hasMore())
{
LdapEntry nextEntry = null;
try
{
nextEntry = lsc.next();
}
catch (LdapException e)
{
// Exception is thrown, go for next entry
continue;
}
LdapAttribute attribute = nextEntry.getAttribute("mail");
email = attribute.StringValue;
}
}
catch (Exception e)
{
// exception
}
Disconnect();
return email;
}
public static void test()
{
string ldapHost = "ssuzdc3";
int ldapPort = 389;
try
{
// Creating an LdapConnection instance
LdapConnection ldapConn = new LdapConnection();
// Connect function will create a socket connection to the server
ldapConn.Connect(ldapHost, ldapPort);
// Bind function with null user dn and password value will perform anonymous bind
// to LDAP server
ldapConn.Bind(null, null);
// Searches in the Marketing container and return all child entries just below this
// container i.e. Single level search
LdapSearchResults lsc = ldapConn.Search("OU=Users,OU=Suzhou,DC=charry,DC=org",
LdapConnection.SCOPE_ONE,
"sAMAccountName=qinick",
null,
false);
while (lsc.hasMore())
{
LdapEntry nextEntry = null;
try
{
nextEntry = lsc.next();
}
catch (LdapException e)
{
Console.WriteLine("Error: " + e.LdapErrorMessage);
// Exception is thrown, go for next entry
continue;
}
Console.WriteLine("n" + nextEntry.DN);
LdapAttributeSet attributeSet = nextEntry.getAttributeSet();
System.Collections.IEnumerator ienum = attributeSet.GetEnumerator();
while (ienum.MoveNext())
{
LdapAttribute attribute = (LdapAttribute)ienum.Current;
string attributeName = attribute.Name;
string attributeVal = attribute.StringValue;
Console.WriteLine(attributeName + "value:" + attributeVal);
}
}
ldapConn.Disconnect();
}
catch (Exception e)
{
string x = e.Message;
}
Console.Read();
}
}
}
[ad]
Problem with JFreechart
一个基于Tomcat的程序里面用到了JFreeChart,偶尔用浏览器访问时,PC端安装的XManager的XServer会自动的打开,然后Tomcat就莫名其妙的挂掉。在没有安装XManager的PC上通常就不会出这种情况。查看一下log,如下:
Can’t connect to X11 window server using ‘:0.0′ as the value of the DISPLAY variable.
搜之,此乃awt的bug。加入以下参数启动即可:
-Djava.awt.handless=true
如果是Tomcat,把上面的参数加到环境变量:CATALINA_OTPS里即可。
[ad]
Upload the 3rd party artifact to local Archiva
Maven is a powerful build tool, more and more developers migrate to it, so do I. Below is the command for deploy the 3rd artifact to your local Archiva:
mvn deploy:deploy-file -DrepositoryId=internal -Durl=http://ssuzsws02:8080/archiva/repository/internal -DgroupId=com.amd.sws -DartifactId=mysql-connector-java -Dversion=3.0.17 -Dpackaging=jar -Dfile=test.jar
but before you issue this command, you should add the following lines to your local settings.xml
<servers> <server> <id>internal</id> <username>admin</username> <password>mypassword</password> </server> </servers>
FYI.: when you upload your jar file to Archiva, you can’t see it immediately, please be patient and wait several minutes, the artifact list will be synced soon.
Hibernate, SocketTimeOutException错误
项目中用到Hibernate,部署的时候发现,过了一段时间后,Hibernate就不能正常工作了,时间很有规律,通常是在部署后的若干个小时。错误日志如下:
** BEGIN NESTED EXCEPTION **
java.net.SocketTimeoutException
MESSAGE: Read timed outSTACKTRACE:
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(Unknown Source)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1392)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:1539)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:1930)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1168)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1279)
at com.mysql.jdbc.MysqlIO.sqlQuery(MysqlIO.java:1225)
at com.mysql.jdbc.Connection.execSQL(Connection.java:2278)
at com.mysql.jdbc.Connection.execSQL(Connection.java:2237)
at com.mysql.jdbc.Connection.execSQL(Connection.java:2218)
at com.mysql.jdbc.Connection.commit(Connection.java:1155)
at org.hibernate.transaction.JDBCTransaction.commitAndResetAutoCommit(JDBCTransaction.java:139)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:115)
at com.amd.BizB.test(BizB.java:27)
at org.apache.jsp.index_jsp._jspService(index_jsp.java:109)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:393)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:320)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:266)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:261)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:581)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
at java.lang.Thread.run(Unknown Source)
最后发现是由于配置文件Hibernate.cfg.xml没有写好,少加了若干的项(注意黑体部分)
<property name=”connection.autocommit”>true</property>
<property name=”connection.url”>jdbc:mysql://ssuzsws01:3306/foo?autoReconnect=true</property>
加了这些选项后,就正常了,希望对遇到类似问题的朋友有帮助。
为什么不用SEH
前些时候,在论坛上看到一个朋友说SEH怎么怎么不好,一定不要用它。其实存在即合理。就像GOTO,不能因为它破坏了程序的流程,就不用它,适当的使用,还是可以事半功倍的。
大家知道SEH是Windows操作系统提供的一种异常处理机制,它和C++无关。在Compiler编译的时候,就把这个机制加入了我们的程序中。在VC下可以用__try, __finally, __except, __leave等关键字来标识。由于SEH可以捕获硬件异常(Hardware Exception)和软件异常(Software Exception),它比C++的异常机制能捕获更多的异常,所以有朋友不喜欢这点,认为它掩盖了错误。其实这种说法是也是合情合理的,毕竟掩盖错误不是最好的解决方案,找出问题的所在才是我们应该做的。可是在现实中,我们不可能找到所有的bug,或者由于时间的关系,来不及修补这个bug,不如先用SEH挡一挡,何尝不可。
就像我之前的一个项目,程序在一个地方偶尔会Crash掉,而且这个地方如果不能正常执行丝毫不影响整个程序的运作,不会对用户造成损失,在找出问题真正的原因之前,我们完全可以用SEH捕获异常。
下面的例子也是一个SEH优势的体现
BOOL SafeDiv(INT32 dividend, INT32 divisor, INT32 *pResult)
{
__try
{
*pResult = dividend / divisor;
}
__except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
return FALSE;
}
return TRUE;
}
SafeDiv用来做除法操作,它的返回值指出函数是否执行成功。pResult指向最终的结果,如果不用SEH,这类Hardware Exception会导致程序Crash,这里引入了SEH,我们在发现有除零错误的时候,让函数返回FALSE,调用处通过检查函数的返回值就可以判断除法运算是否成功,没必要就因为一个除零错误导致程序Crash掉。
由于SEH是Windows操作系统特有的机制,所以它不适合用在那些跨平台的代码,这种情况不用也罢,既然SEH是一个Windows的一个很好的异常处理机制,我们虽不能滥用它,适当的、合理的使用还是值得推荐的。
P.S.
For details about SEH, check this: http://www.google.com/search?hl=en&q=Programming+Applications+for+Microsoft+Windows