slf4j框架源码中是如何实现双重锁的?

阅读分析slf4j的日志源码,发现其中涵盖了许多知识点和优秀的设计,关键它们都是活生生的实践案例。因此专门写成系列文章与大家分享,欢迎持续关注。第1篇。

前言

阅读源码,必然需要先将源代码下载到本地,本篇为本系列第一篇,因此特意建议大家在阅读本篇文章时看一下配套的源代码(持续更新)。本文章已经将原有的代码fork到笔者的GitHub仓库,后续代码均以fork过来的代码为基准进行讲解。地址:https://github.com/secbr/slf4j

好了,本篇文章我们就从使用日志框架最常见的入口类及方法入手。当我们使用slf4j时,通常通过如下代码获取对应的Logger:

Logger logger = LoggerFactory.getLogger(NoBindingTest.class);

在LoggerFactory的getLogger方法中,最主要的功能就是获得Logger,获得Logger需要先获得对应的ILoggerFactory,而ILoggerFactory又是通过SLF4JServiceProvider初始化和返回的。

本文重点聊聊获取和初始化SLF4JServiceProvider过程中使用到的双重锁机制。

双重锁实现源码

在LoggerFactory类最后有一个名为getProvider的方法,提供了返回当前正在使用的SLF4JServiceProvider实例的功能。具体代码如下:

static SLF4JServiceProvider getProvider() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                performInitialization();
            }
        }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return PROVIDER;
    case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
        // support re-entrant behavior.
        // See also http://jira.qos.ch/browse/SLF4J-97
        return SUBST_PROVIDER;
    }
    throw new IllegalStateException("Unreachable code");
}

从上面的代码可以大概看出获取SLF4JServiceProvider分两步,第一步就是初始化,第二步就是通过switch来比对当前实例化的状态(或阶段),然后返回对应的实例对象或抛出异常。

其中第一步操作便使用到了双重锁。下面根据代码分析一下源码中双重锁的使用流程。

如果只是简单的使用锁机制,防止重复实例化SLF4JServiceProvider对象,直接在getProvider方法上添加synchronized便可。但这就面临性能问题,每次调用该方法时都是同步处理的。而通常情况下只有第一次初始化时有锁的需求。

那么此时可以将锁缩小范围,判断当前情况,只有当未初始化(UNINITIALIZED)时再进行加锁,然后调用初始化操作。但此时如果初始化操作比较耗时,两个线程判断时都是未初始化,都进行初始化操作,只不过一先一后,就有可能初始化两次。

此时,进入锁之后,再进行一次判断,如果是未初始化再进行初始化,由于此时已经进入了锁内部,判断不会存在并发情况(这里并不完全准确,还涉及到指令重排情况),那么就避免了初始化两次的情况。

同时,经过第一次初始化之后,再次获取单例对象时,每次判断都不符合初始化的条件,也就不会走锁的逻辑,大大提高了并发。

整个双重锁的实现步骤便是:1、判断是否符合初始化条件;2、加锁当前类;3、再次判断是否符合初始化条件;4、初始化。

单例模式中的双重锁

slf4j框架源码中的双重锁主要是用来初始化SLF4JServiceProvider对象,基本上就是我们在实践或面试过程中经常提到的单例模式。

而且通过slf4j的源码可以看出此处的单例模式属于懒汉模式,也就是只有当我们第一次调用LoggerFactory#getLogger方法时才会进行初始化。

下面以一个简单的单例模式再回顾一下双重锁的实现示例:

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}

在上述代码当中我们看到Singleton变量使用到了volatile进行修饰。这是因为synchronized并不是对instance实例进行加锁(此时还并没有实例),所以在线程执行完初始化赋值操作之后,应该将修后的instance立即写入主内存(main memory),而不是暂时存在寄存器或者高速缓冲区(caches)中,以保证新的值对其它线程可见。

另外在上述单例模式中,new指令并不是原子操作,一般分为三步:1、分配对象内存;2、调用构造器方法,执行初始化;3、将对象引用赋值给变量。

而虚拟机在执行的时候并不一定按照上面1、2、3步骤进行执行,会发生“指令重排”,那就有可能执行的顺序为1、3、2。那么,第一个线程执行完1、3之后,第二个线程进来了,判断变量已经被赋值,就直接返回了,此时会便会发生空指针异常。而当对象通过volatile修饰之后,便禁用了虚拟机的指令重排。

因此,此处volatile是必须添加的,有两个作用:保证可见性和禁止指令重排优化。

回到slf4j中返回的成功初始化的对象PROVIDER时,PROVIDER变量对应的定义同样使用了volatile关键字修饰:

static volatile SLF4JServiceProvider PROVIDER;

如果面试单例模式,你能回答到单例模式的双重锁已经很不错了,但如果还能说清楚待实例化的变量使用volatile修饰的原因,那就完美了。认真阅读源码,还是有所收获的吧。

小结

本篇文章带大家初步了解了slf4j框架中双重锁实现的案例,同时分析了基本的原理与机制,有兴趣的朋友可翻阅一下相关源码,更加直观的进行学习。下篇文章我们继续阅读源码,提炼知识点,既学习了源码又学习了知识点,赶紧关注一下吧。

原文链接:《slf4j框架源码中是如何实现双重锁的?


程序新视界

公众号“ 程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

微信公众号:程序新视界

程序新视界 CSDN认证博客专家 Spring 架构 Java
个人公众号【程序新视界】,一个硬技术,软实力同步提升的平台。《Spring Boo技术内幕:架构设计与实现原理》作者,《深入以太坊智能合约开发》联合作者之一。主要从事于三方支付行业。
已标记关键词 清除标记
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质? 你是否想成为一名资深开发人员,想开发别人做不了的高性能程序? 你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹?   那么C++就是你个人能力提升,职业之路进阶的不二之选。 【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。 2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。 3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。 【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署; 2.吊打一切关于C++的笔试面试题; 3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。 【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块 基础篇 本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。 进阶篇 本篇主要讲解编程常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。 提升篇: 本篇可以帮助学员更加高效的进行c++开发,其包含类型转换、文件操作、异常处理、代码重用等内容。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值