【bugfix】纠正博文'扩展log4j系列[二]动态、分文件记log'

原文链接见:http://sw1982.iteye.com/admin/blogs/611545

 

在上文中提到扩展appener方法的实现,结果本周同事在一段代码里面出现了log重复的情况。

 

情况描述是:root log文件输出一次,被我扩展过的分模块appender却根据Executor线程数重复输出n次。经过一番debug(多线程debug真的比较恶心...经验是配合一些sysout查看临界,否则线程被刮起很难模拟并发)

 

test代码如下:(这个testcase可以借鉴的地方是,在线程里面起executor,可以比较方便的mock多线程并发)

 

 

package test;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import com.paipai.core.logging.Logger;
import com.paipai.logicframework.biz.service.BaseService;
import com.paipai.logicframework.common.logging.LoggerUtil;

/**
 * created on 2010-5-6下午02:22:42
 * 
 * @author weisong
 */
public class MutilThreadLog {
	public static void main(String[] args) {

	Work work = new Work();
	work.doA();
       }
}

class Work {
	private ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors
			.newFixedThreadPool(3);
	private static Logger logger = LoggerUtil.getLogger();

	public void doA() {
		String[] sss = { "aa", "bb", "cc", "dd","ee","ff","gg","hh"
				,"ii","jj","kk","ll","mm","nn","oo","pp","qq","xx","yy","zz"};
		for (final String s : sss) {
			Thread thread = new Thread() {
				@Override
				public void run() {
					logger.log("descnotfound", s);
				}
			};
			executor.execute(thread);
		}
	}
}	
 

 

问题分析:(同步块过小)

 

 

		org.apache.log4j.Logger logger = Log4jFactory.getInstance(logName);
		if (logger.getAppender(logName) == null) {
			initBusiLogAppender(logName, busiModule+".log",	logger);
		}

 这段代码,将logger对象的获取变成工厂模式,确保了单例。 getAppender函数通过源码可以看到是synchronized。

  but,但是,initBusiLogAppender这个函数却是线程不安全的!(具体代码请参看本文开头的博文链接)

 

 出现线程不安全的步骤为:

1.当前class实例获取到 logger对象, 这个时候没有"logName"这个appender

2.当前class实例持有一个Executor,启动了多个线程, 每个线程都会去持有同一个logger对象。(多线程访问本地变量,应该是final,或者voliate的。当然这里的logger对象本身就会是final)

3.线程并发,每个线程都会通过if判断条件,进入initBusiLogAppender方法。(虽然getAppender()是一个同步方法,

可是锁的范围并未涉及到if条件内部)

4. initBusiLogAppender重复初始化n次。。然后就悲剧了。

 

 

解决方案:

 

	private void initBusiLogAppender(String appenderName, String logFileName,	org.apache.log4j.Logger  log) {
		synchronized(log) {
			if(log.getAppender(appenderName)!=null) return;
		try {

 

因为走到initBusiLogAppender函数的机会并不多,所以在这个对象上直接做同步。

 


其他的废话:

 

初步看起来log4j也是有点小问题的,比如定义了如下方法:

 

  /**
     Look for the appender named as <code>name</code>.

     <p>Return the appender with that name if in the list. Return
     <code>null</code> otherwise.  */
  synchronized
  public
  Appender getAppender(String name) {
     if(aai == null || name == null)
      return null;

     return aai.getAppender(name);
  }

 按说这么定义之后,一个log对象应该是不允许有name相同的appender。

但是本文所fix的那个bug,确实是因为初始化了多个相同appender导致!

 

跟进aai.getAppender(name)方法:这下好了,居然是遍历list取第一个name相同的返回,而不管是否存在相同name的appender。

  public
  Appender getAppender(String name) {
     if(appenderList == null || name == null)
      return null;

     int size = appenderList.size();
     Appender appender;
     for(int i = 0; i < size; i++) {
       appender = (Appender) appenderList.elementAt(i);
       if(name.equals(appender.getName()))
	  return appender;
     }
     return null;    
  }
 

 

那么继续跟进addAppender方法,看这里有无保证appender的名字不重复。

  /**
     Attach an appender. If the appender is already in the list in
     won't be added again.
  */
  public
  void addAppender(Appender newAppender) {
    // Null values for newAppender parameter are strictly forbidden.
    if(newAppender == null)
      return;
    
    if(appenderList == null) {
      appenderList = new Vector(1);
    }
    if(!appenderList.contains(newAppender))
      appenderList.addElement(newAppender);
  }
 

  这里使用appenderList.contains(newAppender),vecotr的对象比较来判断appender是否重复,但是不幸的是,

大多数Appender接口实现没有重载Ojbect的hashcode()方法,也就是name相同的appender在这里是可以通过不相等判断。。

你可能感兴趣的:(多线程,thread,log4j,qq,OO)