原文链接见: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在这里是可以通过不相等判断。。
分享到:
相关推荐
`log4j-1.2.11.jar`是Log4j 1.2.11版本的库文件,包含了Log4j的所有类和方法。将这个JAR文件添加到项目的类路径中,即可使用Log4j进行日志记录。这个版本相对于早期版本可能包含了一些性能优化和bug修复。 **三、...
Log4j-1.2.17是Log4j 1.x系列的一个稳定版本,尽管后续发布了Log4j 2.x,但1.2版仍被许多遗留系统和项目广泛使用。这个版本修复了一些已知的bug,提升了性能和稳定性,同时也兼容了当时的Java环境。 总结,Log4j...
1. Log4j 1.2.6:这是Log4j的一个较早版本,提供了基本的日志记录功能,包括定义不同级别的日志(如DEBUG、INFO、WARN、ERROR和FATAL),以及通过配置文件自定义日志输出格式和目的地(如控制台、文件、SMTP等)。...
Log4j 1.2系列是其较为成熟的版本,包含了丰富的日志级别(如DEBUG、INFO、WARN、ERROR、FATAL)和多种日志输出方式(如控制台、文件、网络、数据库等)。 以`log4j.properties`为例,配置文件的基本结构包括以下几...
在配置文件(通常是log4j.properties或log4j.xml)中,我们可以设置日志级别(DEBUG、INFO、WARN、ERROR等)、输出目的地(如ConsoleAppender、FileAppender等)、以及自定义的布局格式(如PatternLayout)等。...
然后配置log4j的属性文件(通常命名为`log4j.properties`或`log4j.xml`),在其中指定Logger、Appender和Layout的详细设置。最后,在代码中通过`org.apache.log4j.Logger`类创建并使用日志器。 **4. 示例配置** ``...
在src源程序包中添加一个log4j.properties文件,用于配置Log4j的日志记录规则。以下是一个简单的log4j.properties文件示例: ```properties log4j.rootLogger=DEBUG, fileLogger,BORROW log4j.appender.fileLogger=...
Log4j的配置文件通常是`log4j.properties`或`log4j.xml`,它定义了Logger、Appender和Layout的具体设置。例如,你可以设置某个类或整个包的日志级别,指定日志输出的位置和格式。 **4. 使用示例** 在描述中提到的...
在Log4j中,通常通过配置文件(如log4j.properties或log4j.xml)来设定日志行为。配置文件中可以定义Loggers、Appenders、Levels和Layouts。例如: ```properties # 配置控制台输出 log4j.appender.stdout=org....
3. `log4j-slf4j-impl-2.8.2.jar` 和 `log4j-slf4j-impl-2.3.jar`:这是SLF4J(Simple Logging Facade for Java)到Log4j的适配器,允许使用SLF4J API的同时,底层日志系统使用Log4j。 4. `log4j-jcl-2.3.jar` 和 `...
SLF4J、slf4j-log4j12和Log4j三者组合,为Java项目提供了灵活且可扩展的日志解决方案。SLF4J作为接口层,允许代码独立于具体日志框架,slf4j-log4j12作为桥接器将SLF4J调用转换为Log4j操作,而Log4j则负责实际的日志...
Log4j的配置通常通过XML或properties文件完成,例如`log4j.properties`或`log4j.xml`。下面是一个简单的配置示例: ```properties # 设置全局日志级别为INFO log4j.rootLogger=INFO, Console, File # 配置控制台...
Apache Log4j 2.0 发布第 4 个 Beta 版本,包括的新特性有: o Added Log4j 2 to SLF4J adapter. o LOG4J2-131: Add SMTPAppender. Thanks to Scott Severtson. o Added hostName and contextName to property ...
在您提供的压缩包文件“log4j_jar包”中,包含了两个不同版本的Log4j JAR文件:`log4j-1.2.8.jar`和`log4j-1.2.16.jar`。这两个版本之间的差异主要在于修复的bug、性能优化以及对新特性的支持。 **1. log4j-1.2.8....
Log4j的配置通常通过一个名为log4j.properties或log4j.xml的文件进行。这个文件定义了Loggers、Appenders和Layouts的具体设置。以下是一个简单的配置示例: ```properties # log4j.properties log4j.rootLogger=...
Log4net是Apache软件基金会开发的一个强大的日志框架,它是log4j(Java版本)在.NET平台上的移植,提供了一套全面的日志记录解决方案。本文将深入探讨log4net的配置及其在.NET应用中的使用。 **1. log4net简介** ...
通常,我们需要创建一个名为`log4j.properties`或`log4j.xml`的配置文件,定义日志输出的级别(如DEBUG, INFO, WARN, ERROR等)、输出目的地(控制台、文件、网络等)以及布局格式。例如,一个简单的`log4j....
SLF4J-log4j12桥接库则是连接SLF4J接口与Log4j实现的桥梁,使得开发者可以使用SLF4J的API,同时利用Log4j进行日志记录。 SLF4J-api-1.5.6.jar是SLF4J API的实现,它包含了一系列的日志记录接口,如`Logger`, `Level...
描述中提到的`PropertyConfigurator.configure`是Log4j中的一个关键类,它的作用是读取并解析`log4j.properties`或`log4j.xml`配置文件。这个配置文件用于定义日志输出的行为,包括日志级别(如DEBUG, INFO, WARN, ...