初识SLF4J之分析一个接口实现

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
  1. 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.
  2. 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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值