守护线程在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. SpringBoot上传文件到本服务器 目录与jar包同级问题

    目录 前言 原因 实现 不要忘记 最后的封装 Follow up   前言 看标题好像很简单的样子,但是针对使用jar包发布SpringBoot项目就不一样了.当你使用tomcat发布项目的时候,上传 ...

  2. 面试问烂的 MySQL 四种隔离级别,看完吊打面试官!

    阅读本文大概需要 5.6 分钟. 来源:网络 什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操 ...

  3. Spring Boot打war包和jar包的目录结构简单讲解

    Spring Boot项目可以制作成jar包和war包,其目录结构是不一样的,具体的如下所示: 1.war包目录结构分析WAR(Web Archivefile)网络应用程序文件,是与平台无关的文件格式 ...

  4. hyper-v显示分辨率如何自动调整

    打开文件/etc/default/grub 找到GRUB_CMDLINE_LINUX_DEFAULT所在行,在最后加上 video=hyperv_fb:[分辨率],比如我想要的分辨率是1600×900 ...

  5. win10-mysql卸载干净

    本文介绍,在Windows10系统下,如何彻底删除卸载MySQL 1.停止MySQL服务 开始——所有应用——Windows管理工具——服务,将MySQL服务停止. 2.卸载mysql server ...

  6. etcd启动报错:couldn't find local name "default" in the initial cluster configuration

    启动etcd的时候报错: # systemctl restart etcd Job for etcd.service failed because the control process exited ...

  7. spring boot整合H2数据库

    一.背景: .H2数据库是一个开源的关系型数据库.H2是一个嵌入式数据库引擎,采用java语言编写,不受 平台的限制,同时支持网络版和嵌入式版本,有比较好的兼容性,支持相当标准的sql标准,支持集群. ...

  8. SDN实验---Ryu的应用开发(四)北向接口RESTAPI

    一:推文 软件定义网络基础---REST API概述 软件定义网络基础---REST API的设计规范 二:掌握Ryu基本RESTAPI使用方法 (一)Ryu的RESTAPI (二) REST应用样例 ...

  9. 关于怎么提取m3u8地址

    摘自: https://blog.51cto.com/4373601/1920758 很长时间没有写博客了,这一段时间比较忙,接下来的日子要坚持写博客了,后期抽空会把这一年多的测试心得补上来,写博客其 ...

  10. vue aes

    npm install crypto-js import CryptoJS from "crypto-js/crypto-js"; const KEY = CryptoJS.enc ...