Simple Logging Facade for Java (SLF4J)
The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time.
示例代码
这里使用了slf4j-simple.jar,来实现slf4j框架。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test {
public static void main(String[] args){
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("test {}", 666);
}
}
对示例代码进行单步调试追踪分析源码
结合具体代码就可以发现上述过程的具体执行流程。
分析设计模式/思路
从上面的代码可以看出,使用SLF4J打印日志,非常简单,只需要从LoggerFactory中获得一个Logger对象,然后调用Logger对像的方法即可。为用户提供了良好的外观——外观模式。
实现SLF4J框架,需要提供ILoggerFactory的实现类(例子中是SimpleLoggerFactory),以及生产工厂对象的StaticLoggerBinder类(指定当前日志框架使用的日志工厂)。此外,SLF4J的Logger也是一个接口,也需要实现,debug可以发现例子中的Logger最高层实现类是SimpleLogger。笔者分析了GitHub上的一个简单的实例,链接地址:https://github.com/my-helloworld/slf4j-demo。
跟踪logger.info("test {}", 666);
可发现代码只是调用了info方法,然后把日志信息打印到控制台,而https://github.com/my-helloworld/slf4j-demo这个设计则是把打印日志变成事件,放入事件队列中,异步打印日志。
疑问追踪之Volatile关键字
在单步调试过程中,发现LoggerFactory的一个方法中使用了Volatile关键字修饰的INITIALIZATION_STATE变量(static volatile int INITIALIZATION_STATE = UNINITIALIZED;
)。下面使用了Volatile+双重检测机制,保证线程安全。
Variable Visibility Problems
多线程在多个处理器上执行:
public class SharedObject {
public int counter = 0;
}
对于上面的代码,考虑一个场景:Imagine too, that only Thread 1 increments the counter variable, but both Thread 1 and Thread 2 may read the counter variable from time to time.
If the counter variable is not declared volatile there is no guarantee about when the value of the counter variable is written from the CPU cache back to main memory. This means, that the counter variable value in the CPU cache may not be the same as in main memory. This situation is illustrated here:
The Java volatile Visibility Guarantee
By declaring the counter variable volatile all writes to the counter variable will be written back to main memory immediately. Also, all reads of the counter variable will be read directly from main memory.
public class SharedObject {
public volatile int counter = 0;
}
If, however, both T1 and T2 were incrementing the counter variable, then declaring the counter variable volatile would not have been enough.
Full volatile Visibility Guarantee
Actually, the visibility guarantee of Java volatile goes beyond the volatile variable itself. The visibility guarantee is as follows:
- If Thread A writes to a volatile variable and Thread B subsequently
reads the same volatile variable, then all variables visible to
Thread A before writing the volatile variable, will also be visible
to Thread B after it has read the volatile variable. - If Thread A reads a volatile variable, then all all variables visible
to Thread A when reading the volatile variable will also be re-read
from main memory.(写也类似)
下面是写:
public class MyClass {
private int years;
private int months
private volatile int days;
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
}
The udpate() method writes three variables, of which only days is volatile.
The full volatile visibility guarantee means, that when a value is written to days, then all variables visible to the thread are also written to main memory. That means, that when a value is written to days, the values of years and months are also written to main memory.
下面是读:
When reading the values of years, months and days you could do it like this:
public class MyClass {
private int years;
private int months
private volatile int days;
public int totalDays() {
int total = this.days;
total += months * 30;
total += years * 365;
return total;
}
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
}
Notice the totalDays() method starts by reading the value of days into the total variable. When reading the value of days, the values of months and years are also read into main memory. Therefore you are guaranteed to see the latest values of days, months and years with the above read sequence.
Instruction Reordering Challenges
public class MyClass {
private int years;
private int months
private volatile int days;
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
}
Once the update() method writes a value to days, the newly written values to years and months are also written to main memory. But, what if the Java VM reordered the instructions, like this:
public void update(int years, int months, int days){
this.days = days;
this.months = months;
this.years = years;
}
The values of months and years are still written to main memory when the days variable is modified, but this time it happens before the new values have been written to months and years. The new values are thus not properly made visible to other threads. The semantic meaning of the reordered instructions has changed.
Java has a solution for this problem, as we will see in the next section.
The Java volatile Happens-Before Guarantee
- Reads from and writes to other variables cannot be reordered to
occur after a write to a volatile variable, if the reads / writes
originally occurred before the write to the volatile variable. The
reads / writes before a write to a volatile variable are guaranteed
to “happen before” the write to the volatile variable. Notice that
it is still possible for e.g. reads / writes of other variables
located after a write to a volatile to be reordered to occur before
that write to the volatile. Just not the other way around. From
after to before is allowed, but from before to after is not allowed. - Reads from and writes to other variables cannot be reordered to
occur before a read of a volatile variable, if the reads / writes
originally occurred after the read of the volatile variable. Notice
that it is possible for reads of other variables that occur before
the read of a volatile variable can be reordered to occur after the
read of the volatile. Just not the other way around. From before to
after is allowed, but from after to before is not allowed.
参考资料
[1] SLF4J官网:https://www.slf4j.org/index.html
[2] https://www.slf4j.org/apidocs/org/slf4j/package-summary.html
[3] 《设计模式之禅》
[4] Java并发编程网:http://ifeve.com/
[5] Jakob Jenkov的并发网:http://tutorials.jenkov.com/java-concurrency/volatile.html
[6] InfoQ