守护线程在logback中的使用

先说问题,在java应用中,logback的异步Appender是怎么在主线程结束后,停下来的?

复盘

我在一个logback的测试用例中,写了这样的代码和logback的配置:

// 一个测试类,main函数中简单的打印了几行日志
public class Test { private static final Logger LOGGER = LoggerFactory.getLogger(Test.class); public static void main(String[] args) {
LOGGER.info("test log 1");
LOGGER.info("test log 2");
} }

这是我使用的logback的配置,为root-logger配置了两个appender,一个是ConsoleAppender,会将日志内容输出到控制台中,另一个是套在AsyncAppender中的FileAppender,它采用异步的方式将日志内容输出到文件中

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/usr/local/test.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<root level="debug">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC"/>
</root>
</configuration>

当我正常执行Test.main(),观察控制台和文件中都正常输出了日志。然后,为了观察AsyncAppender的原理,我在AsyncAppender的父类AsyncAppenderBase中,看了一下大致的原理:AsyncAppenderBasestart()方法中,调用了worker.start(),其实就是启动了一个线程。截取Worker.run()方法中比较重要的代码,就是在一个while循环中,从阻塞队列获取ILoggerEvent,我在这里打了断点,希望追进具体的Appender中看一下

while (parent.isStarted()) {
try {
E e = parent.blockingQueue.take();
// 这里打了断点
aai.appendLoopOnAppenders(e);
} catch (InterruptedException ie) {
break;
}
}

当执行到断点处,观察了一会儿变量后,点击步进,突然发现整个java应用结束了。没有任何异常发生。这种情况我重试了很多次,不一定在断点处结束,可能再往下几步,但是debug模式下,java应用一定会在某个点毫无征兆的“正常结束”

我当时去确认了一下,控制台中两行日志都打印了出来,这说明主线程一定执行完了,但是我当时的知识储备是,java主线程结束时,如果子线程没有结束,那么会等待子线程结束后,JVM才会执行最后的结束任务。(这里完全不记得守护线程的概念)

!!!上面这个结论是错误的

然后就想通过jstack来观测一下,当main-Thread结束后,线程到底是什么情况:

这里发现:

DestroyJavaVM线程居然处于RUNNABLE阶段,可是还有子线程在执行啊,为什么它就开始了

带着疑问再看一下AsyncAppender-Worker-ASYNC线程,这就是异步日志打印任务的线程了,也是RUNNABLE状态,但是它好像是一个守护线程(daemon),这才想起来java线程的基础知识,用户线程守护线程,这里直接贴出来ThreadsetDaemon方法,代码注释中说:标记该线程为user thread或daemon thread,JVM只会在当前运行的线程,全部都是daemon thread时,才会结束。

再看看AsyncAppenderBase中的start()方法:

可以看到,它将worker指定为了daemon线程,问题到这里其实也就解决了。

结论

这次碰到的问题,其实并不复杂,主要还是知识储备的问题

java多线程中,用户线程和守护线程在能力上并没有什么区别,最重要的区别是当JVM退出时,如果还有存活的用户线程,则不会退出;如果只剩下守护线程了,那么JVM则会杀掉所有的守护线程,并退出。

PS:

没人知道我DEBUG了多久。。。简直超出认知范围了!!!差点以为是玄学

守护线程在logback中的使用 - 论基础知识的重要性的更多相关文章

  1. Java中实现异常处理的基础知识

    Java中实现异常处理的基础知识 异常 (Exception):发生于程序执行期间,表明出现了一个非法的运行状况.许多JDK中的方法在检测到非法情况时,都会抛出一个异常对象. 例如:数组越界和被0除. ...

  2. react组件在项目中的应用(基础知识)

    上图我是定义了5个模块,全部都渲染在一个组件里面.可以先看看我的代码结构 我将Hello文件夹下的index.jsx文件作为父组件,最后渲染在根组件中. 那我们怎么输出这个Hello组件呢?要达到上图 ...

  3. 【Unity】6.1 Unity中的C#脚本基础知识

    分类:Unity.C#.VS2015 创建日期:2016-04-16 一.简介 1.常用的C#数据类型 这里简单介绍用Unity开发游戏时,最常用的一些数据类型. (1)基本类型 int.float. ...

  4. C++面试中可能考察的基础知识(1)

    1 C++中允许函数的嵌套调用,但不允许函数的嵌套定义 2 构建派生类对象时,先调用基类的构造函数,在调用成员对象的构造函数,最后调用派生类构造函数. 3 volatile关键字 volatile提醒 ...

  5. 第11天:JS中变量、字符串基础知识

    一.js简介用来制作页面交互效果,提高用户体验. js页面效果:轮播图.选项卡.地图.表单验证javascript是弱变量类型的语言,变量只需要用var来声明.而java要根据变 量类型来声明, in ...

  6. java守护线程的理解

    package daemonThread; /*setDaemon(true)方法将线程设置为守护线程,线程的Daemon默认值为false * 只要当前JVM实例中存在任何一个非守护线程没有结束,守 ...

  7. 多线程系列 - 基础篇01 - 线程基本概念 & 线程优先级 & 守护线程 60%

    1.什么是线程 将线程理解为轻量级进程,它与进程的最大的区别是: 多个线程共享一个进程资源: 对于OS的许多资源的分配和管理(如内存)通常都是进程级别的,线程只是os调度的最小单位: 相对于进程来说更 ...

  8. Java 守护线程(Daemon) 例子

    当我们在Java中创建一个线程,缺省状态下它是一个User线程,如果该线程运行,JVM不会终结该程序.当一个线被标记为守护线程,JVM不会等待其结束,只要所有用户(User)线程都结束,JVM将终结程 ...

  9. day36 joinablequeue、多线程理论、多线程的两种使用方式、守护线程、互斥锁、死锁、递归锁、信号量

    1.joinablequeue队列 joinablequeue与queue一样,也是一种队列,其继承自queue,也有queue中的put 与get 方法,但是在joinablequeue中有自己的 ...

随机推荐

  1. Linux搭建简单的http文件服务器111

    http://192.168.31.69:8090/file/http://47.92.90.25:21888/file/在Ubuntu中通过apt-get install apache2 安装apa ...

  2. Java 整数间的除法运算如何保留所有小数位?

      1.情景展示 double d = 1/10; System.out.println(d); 返回的结果居然是0.0!这是怎么回事儿? 2.原因分析 第一步:你会发现用运算结果也可以用int类型接 ...

  3. ubuntu笔记1-vim安装报错

    ubuntu安装vim的时候,报错提示:vim : 依赖: vim-common (= 2:7.3.429-2ubuntu2) 但是 2:7.3.429-2ubuntu2.1 正要被安装 说明既存的v ...

  4. 2018ECNA Difference[时空复杂度]

    目录 题干 代码和解释 题干 代码和解释 本题给出一个数列的第一个数A(1),要求找出m第一次出现(直接出现在数列中或是数列中某两项的差的绝对值)在这个数列的第几步中.数列递推公式:A(n+1)=A( ...

  5. 微信小程序图片宽度100%,高度自适应

    实现图片自适应,按照一般情况只需设置: img { width: 100%; height: auto; } 但是微信小程序里是特例,需要image标签上设置属性mode=widthFix,就是hei ...

  6. android测试和iOS测试的区别

    一.常识性区别 二.导航方式 iOS:Tab放在页面底部,不能通过滑动来切换,只能点击.也有放在上面的,也不能滑动,但有些Tab本身可以滑动,比如天猫的.还有新闻类的应用. Android:一般放在页 ...

  7. coroutine闲谈

    coroutine居然能被吹到这种地步

  8. SpringMVC中使用到DataBinder的类

    RedirectAttributesMethodArgumentResolverRequestResponseBodyMethodProcessorServletModelAttributeMetho ...

  9. sails0.12相关命令

    sails最新版本是1.2.3 如果要创建0.12的项目要使用以下命令 npm install sails@0.12 -g sails -vsails new myweb1npm audit fixc ...

  10. 【Base】死锁产生的四个必要条件

    死锁产生的四个必要条件 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放. ...