log4j日志配置文件中的%X{TransId}是怎么动态获取值的
在Log4j中,%X{TransId}
是用来从MDC(Mapped Diagnostic Context)中获取值的占位符。MDC 是 Log4j 提供的一种机制,用于在同一个线程的不同日志记录中传递上下文信息。通过 MDC,你可以在日志中添加一些动态的上下文信息,比如事务ID、用户ID等。
要动态设置 TransId
的值,你需要在代码中使用 MDC.put
方法。以下是一个示例:
java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
public class Log4jExample {
private static final Logger logger = LogManager.getLogger(Log4jExample.class);
public static void main(String[] args) {
// 设置TransId的值
ThreadContext.put("TransId", "12345");
// 记录日志
logger.info("This is a log message with TransId.");
// 清除TransId的值
ThreadContext.clearMap();
}
}
在上面的代码中,我们使用 ThreadContext.put("TransId", "12345")
来设置 TransId
的值。然后在日志配置文件中使用 %X{TransId}
占位符来引用这个值。
例如,log4j2.xml 配置文件可以这样配置:
xml
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg %X{TransId}%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
在这个配置中,%X{TransId}
会被替换为 ThreadContext
中 TransId
的值,即 12345
。
总结 :通过在代码中使用 ThreadContext.put("TransId", "value")
方法设置 TransId
的值,然后在日志配置文件中使用 %X{TransId}
占位符来引用这个值,从而实现动态获取和记录 TransId
。
在Log4j 2中,ThreadContext
是用来替代 Log4j 1.x 中的 MDC
的。对于 Log4j 1.x,你需要使用 MDC.put
方法来设置上下文信息。以下是使用 Log4j 1.x 的示例:
java
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
public class Log4jExample {
private static final Logger logger = Logger.getLogger(Log4jExample.class);
public static void main(String[] args) {
// 设置TransId的值
MDC.put("TransId", "12345");
// 记录日志
logger.info("This is a log message with TransId.");
// 清除TransId的值
MDC.clear();
}
}
在上面的代码中,我们使用 MDC.put("TransId", "12345")
来设置 TransId
的值。然后在日志配置文件中使用 %X{TransId}
占位符来引用这个值。
例如,log4j.properties 配置文件可以这样配置:
properties
log4j.rootLogger=INFO, Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m %X{TransId}%n
在这个配置中,%X{TransId}
会被替换为 MDC
中 TransId
的值,即 12345
。
总结 :对于 Log4j 1.x,通过在代码中使用 MDC.put("TransId", "value")
方法设置 TransId
的值,然后在日志配置文件中使用 %X{TransId}
占位符来引用这个值,从而实现动态获取和记录 TransId
。对于 Log4j 2.x,使用 ThreadContext.put
方法来实现相同的功能。
ThreadLocal
是 Java 提供的一种机制,用于在每个线程中存储独立的变量副本。它可以确保变量在多线程环境下的线程安全性,而不需要显式的同步。每个线程都可以独立地修改自己的副本,而不会影响其他线程的副本。
ThreadLocal
的基本原理
-
独立副本 :每个线程都有一个独立的变量副本,这些副本存储在
ThreadLocal
对象中。每个线程对其副本的修改不会影响其他线程的副本。 -
内部存储 :
ThreadLocal
通过Thread
类中的一个内部ThreadLocalMap
来存储每个线程的变量副本。ThreadLocalMap
是一个自定义的哈希表,键是ThreadLocal
对象,值是线程的变量副本。 -
访问机制 :当线程访问
ThreadLocal
变量时,ThreadLocal
会根据当前线程获取对应的变量副本。如果当前线程没有对应的副本,ThreadLocal
会创建一个新的副本并存储在ThreadLocalMap
中。
ThreadLocal
的主要方法
get()
:返回当前线程的变量副本。如果当前线程没有对应的副本,则调用initialValue()
方法创建一个新的副本。set(T value)
:设置当前线程的变量副本。remove()
:移除当前线程的变量副本,防止内存泄漏。
示例代码
以下是一个简单的示例,展示了如何使用 ThreadLocal
:
java
public class ThreadLocalExample {
// 创建一个ThreadLocal变量
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
// 创建两个线程
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
threadLocal.set(threadLocal.get() + 1);
System.out.println(Thread.currentThread().getName() + " - " + threadLocal.get());
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
threadLocal.set(threadLocal.get() + 1);
System.out.println(Thread.currentThread().getName() + " - " + threadLocal.get());
}
});
thread1.start();
thread2.start();
}
}
在这个示例中,每个线程都有一个独立的 ThreadLocal
变量副本,互不干扰。
内部实现
ThreadLocal
的内部实现主要依赖于 ThreadLocalMap
,这是一个自定义的哈希表,存储在每个线程的 Thread
对象中。以下是一些关键点:
-
ThreadLocalMap :
ThreadLocalMap
是ThreadLocal
的静态内部类,用于存储每个线程的变量副本。它使用ThreadLocal
对象作为键,变量副本作为值。 -
Entry :
ThreadLocalMap
中的每个条目是一个Entry
对象,包含一个弱引用的ThreadLocal
键和一个强引用的值。 -
垃圾回收 :由于
ThreadLocal
键是弱引用,当ThreadLocal
对象被垃圾回收时,ThreadLocalMap
中对应的条目会被自动清除,防止内存泄漏。
内存泄漏问题
使用 ThreadLocal
时需要注意内存泄漏问题。由于 ThreadLocalMap
中的键是弱引用,但值是强引用,如果不显式调用 remove()
方法,可能会导致内存泄漏。因此,建议在使用完 ThreadLocal
变量后,显式调用 remove()
方法清除变量副本。
java
threadLocal.remove();
总结 :ThreadLocal
提供了一种在多线程环境下存储和访问独立变量副本的机制,确保线程安全性。其内部通过 ThreadLocalMap
实现,每个线程都有一个独立的变量副本。使用时需要注意内存泄漏问题,建议在使用完后显式调用 remove()
方法清除变量副本。
ThreadLocalMap
是 ThreadLocal
类的一个静态内部类,用于存储每个线程的变量副本。它是 ThreadLocal
实现的核心部分。ThreadLocalMap
通过在每个线程的 Thread
对象中维护一个哈希表来实现这一功能。
ThreadLocalMap
的结构
ThreadLocalMap
是一个自定义的哈希表,包含以下几个关键部分:
-
Entry :
ThreadLocalMap
中的每个条目是一个Entry
对象。Entry
是ThreadLocalMap
的静态内部类,包含一个弱引用的ThreadLocal
键和一个强引用的值。javastatic class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
-
Table :
ThreadLocalMap
使用一个数组table
来存储Entry
对象。数组的大小是2的幂次方,以便于哈希冲突的处理。javaprivate Entry[] table;
-
Threshold :
ThreadLocalMap
维护一个阈值threshold
,用于控制何时进行扩容。默认情况下,阈值是数组大小的2/3。javaprivate int threshold;
ThreadLocalMap
的主要方法
-
set() :将值存储到
ThreadLocalMap
中。如果键已经存在,则更新值;如果键不存在,则插入新的条目。javaprivate void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len - 1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
-
get() :从
ThreadLocalMap
中获取值。如果键存在,则返回对应的值;如果键不存在,则返回null
。javaprivate Object getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e.value; else return getEntryAfterMiss(key, i, e); }
-
remove() :从
ThreadLocalMap
中移除键值对。javaprivate void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len - 1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
内存泄漏问题
由于 ThreadLocalMap
中的键是弱引用,当 ThreadLocal
对象被垃圾回收时,键会变成 null
,但值仍然存在。这可能导致内存泄漏。因此,建议在使用完 ThreadLocal
变量后,显式调用 remove()
方法清除变量副本。
java
threadLocal.remove();
总结
ThreadLocalMap
是ThreadLocal
的静态内部类,用于存储每个线程的变量副本。Entry
是ThreadLocalMap
的静态内部类,包含一个弱引用的ThreadLocal
键和一个强引用的值。ThreadLocalMap
使用一个数组table
来存储Entry
对象,并通过哈希算法和线性探测解决哈希冲突。- 主要方法 包括
set()
、get()
和remove()
,用于存储、获取和移除键值对。 - 内存泄漏问题 :由于键是弱引用,值是强引用,建议在使用完
ThreadLocal
变量后显式调用remove()
方法清除变量副本。