在log4j1.2的版本里,发现有两把性能低下的锁,一把就是在Category的callAppender方法里,代码如下
public
void callAppenders(LoggingEvent event) {
int writes = 0;
for(Category c = this; c != null; c=c.parent) {
// Protected against simultaneous call to addAppender, removeAppender,...
synchronized(c) {
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
为了防止在记录日志的时候appender被更新,所以加了一把独占锁,如果一个线程正在记录日志,那么其他线程用到了这个logger的都会在这里等待,从实际运行情况来看,很少有经常变动的appender,所以这种情况依然是读多写少,个人觉得极其适合用ReentrantReadWriteLock来处理,把synchronized换成读锁,在addAppender或者removeAppender上增加写锁,这样就很大程度上避免了锁竞争,事实上最新版的log4j就替换为了读写锁,只不过是用来非重入的读写锁。改完之后性能大幅度提高了。修改后的代码如下
public void callAppenders(LoggingEvent event) {
int writes = 0;
for (Category c = this; c != null; c = c.parent) {
// Protected against simultaneous call to addAppender,
// removeAppender,...
try {
c.readLock.lock();
if (c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
} finally {
c.readLock.unlock();
}
// synchronized (c) {
// if (c.aai != null) {
// writes += c.aai.appendLoopOnAppenders(event);
// }
// }
if (!c.additive) {
break;
}
}
if (writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
另一个锁在AppenderSkeleton的doAppend方法上,因为大部分appender都从这里派生,真正执行写任务的都在子类,但是框架义无反顾地走doAppend上加了一把锁,无论子类用不用锁,都已经锁上了。所以如果子类无需并发处理的时候,可以重写了doAppend方法,去掉这把锁,这样性能又会大幅度提高。
public synchronized void doAppend(LoggingEvent event) {
if (closed) {
LogLog.error("Attempted to append to closed appender named ["
+ name + "].");
return;
}
if (!isAsSevereAsThreshold(event.getLevel())) {
return;
}
Filter f = this.headFilter;
FILTER_LOOP: while (f != null) {
switch (f.decide(event)) {
case Filter.DENY:
return;
case Filter.ACCEPT:
break FILTER_LOOP;
case Filter.NEUTRAL:
f = f.next;
}
}
this.append(event);
}
修改了这2把锁后,性能就很高了,其实没必要对日志做异步处理,异步处理的伸缩性做的好不好,以及他的饥饿策略等等,都有可能带来不理想的结果。