sinboy的菜地

一份耕耘,一份收获

张新波ID:sinboy
83910次访问,排名1116好友4人,关注者26
sinboy的文章
原创 49 篇
翻译 0 篇
转载 2 篇
评论 106 篇
sinboy的公告

喜欢中文分词技术的朋友请入http://groups.google.com/group/ictclas

最近评论
yuzishui:呵呵,都是要源码的。
谢谢您的知识.
也希望您能坚持下去.
支持.
周梁:怎么就这两句话?赶紧加强啊。
xazl.ia.ac.cn@gmail.com
周梁:我刚开始看,这里我觉得楼主可能没有理解,
作者的原意是为了防止权重相同的节点,<判断就是和=判断区别开,如果出现=,i就不会增加,继续会在这个权重下面进行插入父节点。呵呵。
可以参考他的论文一段话:

如果两条或两条以上路径长度相等,那么他们的长度并列第i,都要列入粗分结果集,而且不影响其他路径的排列序号,最后的粗分结果集合大小大于或等于N。
houlc:我的邮箱是houlc@foxmail.com
houlc:你能发给我一份吗,我也想在项目中使用log4j,想参考参考你是怎么用的.
文章分类
收藏
    相册
    IO
    TPTP
    友情链接
    DanceFire的BLOG
    Justin的BLOG
    Martin Fowler
    博客园设计模式
    吕震宇的BLOG
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 关于在Log4j中使用JDBCAppender时出现死循环的问题收藏

    新一篇: 单源点最短路径Dijkstra算法的JAVA实现 | 旧一篇: 关于JAVA中线程同步的性能测试

    APACHE的log4j是一个非常好用的日志记录管理工具,可以实现到屏幕、文件、远程数据库、自动发送邮件等,功能强大而又简单易用。
     
    但是今天在使用经过扩展的JDBCAppender时却碰到一个莫名其妙的问题,描述如下:
    1.为了在日志向数据输出时每次都创建新的连接,在原来JDBCAppender的基础上进行扩展,使用自己写的数据库连接池,主要是重写getConnectioin()和closeConnection()两个方法,未改动前的源代码如下:

    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.ArrayList;

    import org.apache.log4j.jdbc.JDBCAppender;
    import org.apache.log4j.spi.LoggingEvent;

    import com.gftech.util.GFConn;
    import com.gftech.util.GFDB;

    public class JDBCExtAppender extends JDBCAppender {
     protected String driver;
     public static GFDB gfdb;
     private ArrayList<GFConn> tempList;

     public JDBCExtAppender() {
      super();
      tempList = new ArrayList<GFConn>();
     }

     /**
      * Override this to return the connection to a pool, or to clean up the
      * resource.
      *
      * The default behavior holds a single connection open until the appender is
      * closed (typically when garbage collected).
      */
     protected void closeConnection(Connection con) {
      if (con != null && tempList != null) {
       for (int i = 0; i < tempList.size(); i++) {
        GFConn gfconn = tempList.get(i);
        if (con == gfconn.getConn()) {
         gfconn.close();
         tempList.remove(i);
         // System.err.println("remove conn:"+con);
         break;
        }
       }
      }
     }

     /**
      * Override this to link with your connection pooling system.
      *
      * By default this creates a single connection which is held open until the
      * object is garbage collected.
      */
     protected Connection getConnection() throws SQLException {
      
      if (gfdb == null) {
       gfdb = new GFDB("db9", driver, databaseURL, databaseUser, databasePassword);
       
      }
      if (gfdb != null) {
       GFConn gfconn = gfdb.getConn();
       if (gfconn != null) {
        connection = gfconn.getConn();
        tempList.add(gfconn);
       } 
      }  
      return connection;
     }

     public void close() {
      flushBuffer();

      this.closed = true;
     }
     
     public String getLogStatement(LoggingEvent event){
      StringBuffer sbuf=new StringBuffer();
      sbuf.append(layout.format(event));
      if (layout.ignoresThrowable ()) {
       sbuf.delete(sbuf.length()-2,sbuf.length() );
       String[] s = event.getThrowableStrRep();
       if (s != null) {
        for (int j = 0; j < s.length; j++) {
         sbuf.append("\r\n");
          sbuf.append(s[j]);
        }
       }
       sbuf.append("')");
      }
      
      return sbuf.toString() ;
     }

     public void setDriver(String driverClass) {
      driver = driverClass;

     }

    数据库连接池的主要源代码如下:

    public class GFDB {
     private String dbName;

     private ArrayList<GFConn> idleConnPool;// 空闲池

     private ArrayList<GFConn> usingConnPool;// 使用池

     private final int maxConns = 150;// 最大连接数目

     private final int maxUserCount = 149;// 每个连接允许的最大用户数目

     private final int timeout = 60000;// 连接的最大空闲时间

     private final int waitTime = 30000;// 用户的最大等待时间

     private String dbUrl = null;// 连接地址

     private String dbDriver = null;// 数据库驱动

     private String dbUser = null;// 登陆用户名

     private String dbPwd = null;// 登陆密码

     private ThreadGroup group = null;
     

     static Logger logger=Logger.getLogger(GFDB.class); 

     public GFDB(String dbName, String driver, String url, String user,
       String pwd) {

      this.dbName = dbName;
      dbDriver = driver;
      dbUrl = url;
      dbUser = user;
      dbPwd = pwd;
      if (driver != null && url != null && user != null && pwd != null)
       init();
     }

     private void init() {

      Connection conn = null;
      GFConn _conn = null;

      // init the connection pool
      idleConnPool = new ArrayList<GFConn>(0);
      usingConnPool = new ArrayList<GFConn>(0);

      conn = buildConn();
      if (conn != null) {
       _conn = new GFConn();
       _conn.setConn(conn);
       idleConnPool.add(_conn);
      } else {

       try {
        logger.error("\n\n和数据库的连接被重置,试图重新建立连接。。。");
        Thread.sleep(10000);
        init();
       } catch (InterruptedException ex) {
        ex.printStackTrace();
       }
      }

      if (group == null)
       group = new ThreadGroup("ThreadGroup");
      // if the manager thread is not active ,then run it
      if (!isActive(group, "manager"))
       manager();
     }

     /**
      * 连接远程数据库
      *
      * @return 连接成功返回TRUE
      */
     private Connection buildConn() {
      Connection conn = null;
      try {
       Class.forName(dbDriver);
       conn = DriverManager.getConnection (dbUrl, dbUser, dbPwd);
       if (conn != null) {

        String str = "建立和远程数据库" + dbUrl + "的连接:" + conn;
        logger.info(str);
       }

      } catch (SQLException e) {
       e.printStackTrace();

      } catch (ClassNotFoundException e) {
       e.printStackTrace();
      }
      return conn;
     }

     /**
      * 从连接池中取出一个可用的连接. 如果空闲池中有,则从空闲池中取,然后把userCount加一,并把此连接
      * 移动使用池中;否则,判断使用池中是否有可用的连接(一个连接上 的使用用户数目还没有达到最大值即为可用的连接),如果有则在此
      * 连接分配给用户使用,如果使用池中没有可用的连接,并连接的总数目 还没有达到系统限制的最大值,则重新创建一个连接供用户使用,否则
      * 等待一段时间再查看是否有可用连接。
      *
      * @param timeout
      *            最大等待时间,超出此时间则返回NULL
      * @return
      */
     public synchronized GFConn getConn() {
      Connection conn = null;
      GFConn _conn = null;

      if (idleConnPool != null && idleConnPool.size() > 0) {
       // 这个地方可能会出数组越界异常. 估计还是同步问题
       _conn = (GFConn) idleConnPool.remove(0);
       // System.out.println("remove from idleConnPool:"+_conn);
       _conn.setLastAccessTime();
       _conn.changeUserCount(1);
       usingConnPool.add(_conn);
       return _conn;
      } else {
       if (usingConnPool != null) {
        if (usingConnPool.size() > 0) {
         // System.out.println ("usingConnPool.size():"+usingConnPool.size());
         for (int i = 0; i < usingConnPool.size(); i++) {
          _conn = (GFConn) usingConnPool.get(i);
          if (_conn.getUserCount() < maxUserCount) {
           _conn.changeUserCount(1);
           return _conn;
          }

         }
         _conn = null;
        }

        // 如果目前没有可用的连接并且没有达到最大的连接数目,则重新为用户
        // 创建一个连接,否则等待一段时间看是否有连接被释放。
        if (usingConnPool.size() < maxConns) {
         conn = buildConn();
         if (conn != null) {
          _conn = new GFConn();
          _conn.setConn(conn);
          _conn.changeUserCount(1);
          usingConnPool.add(_conn);

          return _conn;
         }
        } else {
         try {
          Thread.sleep(waitTime);
          System.out.println("No usable connection,sleep...");
         } catch (InterruptedException e) {
          e.printStackTrace();
         }

         getConn();
        }
       }
      }
      return null;
     }

    。。。。

    主类里的调用代码:

    PropertyConfigurator.configure(ConfParam.LOG4J_PROP_CONF);

    logger.info("load dict");

    问题出来了,在程序启动后,只要一执行完logger.info("load dict");这一句,系统就不停的向数据发起连接,至到资源耗尽为止,百思不得其解,程序原来也是一直是正常的,不管是向文件输出还是向远程数据库输出日志信息都没有问题,现在为何会限入死循环呢?

    仔细分析源代码可以发现,程序在执行logger.info("load dict");后,为了按照LOG4J。properties中定义的数据库输出执行,log4j接口会自动调用JDBCExtAppender当中的getConnection()方法,在此方法中才会创建数据库实例并创建数据库连接。在此方法中执行了这么一句

    gfdb = new GFDB("db9", driver, databaseURL, databaseUser, databasePassword);

    我在调试中发现,只要执行到这一句之后就开始陷入死循环,程序不往下执行获取连接那一步,那说明问题是出在new GFDB()这个地方,可是奇怪的是我以前是正常的呀,并且如果不起用JDBCExtAppender也不会出现这个的问题。再跳到GFDB类里分析,构造函数里面调用了一个init()方法,而init()里面又调用了buildConn()方法,问题就表现在buildConn()里面。

    经过研究发现,在创建连接成功后我调用logger.info()输出了一个创建成功的提示,见上面红色代码,把它屏蔽结果就好了。再仔细琢磨这个方法,又对照执行时打印出来的异常信息是个死循环的引用恍然大悟,死循环就出现在这里。

    原因很简单,我在创建连接成功后,调用了logger。info()方法,而一调用此方法logger为了把日志输出到数据库它就立即又去调用JDBCExtAppender当中的getConn ection()方法,而此时GFDB的实例还没有被创建成功,所以gfdb=null,于是该方法又去调用new GFDB去创建连接,创建成功后又会调用logger。info()。。。

    如此反复,就彻底进入了死循环的怪圈,至到系统资源耗尽。解决的办法很简单,就是在数据库连接池类里面不要用logger来输出日志就行了,全部改成System.out.println()输出,其实原来我就是这样,所以一直挺正常。前几天为了把输出统一起来,也为了能够记录数据库创建的信息,就把标准输出改成了logger输出,导致了现在这一情况的出现。不过有了这样的教训,也有了一个教训,不然自己引用自己,象小猫玩自己抓自己尾巴的游戏,总也抓不住,累都累死。 

    发表于 @ 2007年03月28日 15:21:00|评论(loading...)|编辑

    新一篇: 单源点最短路径Dijkstra算法的JAVA实现 | 旧一篇: 关于JAVA中线程同步的性能测试

    评论

    #lou 发表于2008-05-22 16:57:03  IP: 202.108.57.*
    个人感觉,你的JDBCExtAppender考虑问题比较全面,但是由于东西不全,不能测试。能给一个较完整的吗?万分感谢!Email:makefri3738_cn@sina.com.cn
    #sinboy 发表于2008-05-26 09:53:13  IP: 60.190.244.*
    发你邮箱了,主要用到的几个Class
    #houlc 发表于2008-07-05 20:48:38  IP: 218.60.75.*
    你能发给我一份吗,我也想在项目中使用log4j,想参考参考你是怎么用的.
    #houlc 发表于2008-07-05 20:49:22  IP: 218.60.75.*
    我的邮箱是houlc@foxmail.com
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © sinboy