最近在调研一个分布式分片的任务调度框架的事情, 接触到了 Xxl-job, 在运行这个项目的服务案例时, 作者提供了一个spring-web形式的案例工程, clone到本地编译运行确实没有问题, 同时也部署了中心化管理服务Xxl-job-admin, 还用手动即时触发的方式调度了执行器服务中的任务逻辑;
然后在查阅项目文档时, 作者说明了执行器服务内部是启动了Jetty服务器与调度中心进行通信, 这里就有疑惑了, 既然是内部Jetty通信的方式, 那为何还要以servlet-web项目的形式部署到tomcat容器中呢?直接打包执行器项目为jar, 以普通java应用的方式启动不是更方便吗
通常我们运行一个java程序, 是从一个类的main方法为入口。背后就是JVM启动一个独立的非守护线程(non-daemon), 去执行我们的 static main 方法, 当出现以下情况, 应用程序线程就会被JVM关闭结束掉;
- 应用程序main方法执行完成并返回后, 应用内不存在其他用户线程, JVM就会关闭结束应用
- 应用程序内部调用System.exit(0);, 这样无论应用内是否存在用户线程, 守护线程, 都会强制退出;
再来说说上面, 我想通过直接执行一个static main方法的启动执行器服务(基于spring的), 所以写了下面的代码
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 应用程序启动入口
* @author Hinsteny
* @version $ID: MyJobApplication 2018-06-29 15:01 All rights reserved.$
*/
public class MyJobApplication {
/**
* start spring container
* @param args
*/
public static void main(String[] args) throws InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"appcontext-xxl-job.xml"});
context.start();
//Thread.sleep(1000);
}
}
// "appcontext-xxl-job.xml"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="fileEncoding" value="utf-8" />
<property name="locations">
<list>
<value>classpath*:xxl-job-executor.properties</value>
</list>
</property>
</bean>
<!-- ********************************* 基础配置 ********************************* -->
<!-- 配置01、JobHandler 扫描路径 -->
<context:component-scan base-package="org.hisoka.job" />
<!-- 配置02、执行器 -->
<bean id="xxlJobExecutor" class="com.xxl.job.core.executor.XxlJobExecutor" init-method="start" destroy-method="destroy" >
<!-- 执行器注册中心地址[选填],为空则关闭自动注册 -->
<property name="adminAddresses" value="${xxl.job.admin.addresses}" />
<!-- 执行器AppName[选填],为空则关闭自动注册 -->
<property name="appName" value="${xxl.job.executor.appname}" />
<!-- 执行器IP[选填],为空则自动获取 -->
<property name="ip" value="${xxl.job.executor.ip}" />
<!-- 执行器端口号[选填],为空则自动获取 -->
<property name="port" value="${xxl.job.executor.port}" />
<!-- 访问令牌[选填],非空则进行匹配校验 -->
<property name="accessToken" value="${xxl.job.accessToken}" />
<!-- 执行器日志路径[选填],为空则使用默认路径 -->
<property name="logPath" value="${xxl.job.executor.logpath}" />
<!-- 日志保存天数[选填],值大于3时生效 -->
<property name="logRetentionDays" value="${xxl.job.executor.logretentiondays}" />
</bean>
</beans>
然后运行发行, 应用程序执行完main函数就关闭了, 死活不能起起来后持续运行, 到底啥原因呢?
一番查找后, 大概是这样的, 上面的xml配置文件中, 是调度框架作者在执行器服务的初始化过程中启动了一个守护线程, 内部有启动jetty服务器的操作, 然后jetty内部的启动逻辑中, 又启动了一些列的守护线程(jvm退出监听的)和用户线程(监听端口的), 这样的话, 那我给主程序的main函数最后一句加一行休眠, 一秒钟已经足够jetty内部相关启动程序执行, 创建出用户线程了, 所以休眠一秒后, 虽然main方法结束退出了, 但是应用还是在持续运行啦!
给出个例子看看应用程序到底哪些情况会持续运行不退出呢
package org.hinsteny.jvm.commons;
import java.time.LocalDateTime;
/**
* 一个运行在jvm中的应用程序, 当所有用户线程退出后, 守护线程也就退出了, 然后应用程序便会关闭结束
* ::只要程序中有一个用户线程, 应用程序就不会被jvm关闭结束;
* ::只要程序中只剩有一个守护线程, 应用程序就会被jvm关闭结束;
*
* @author Hinsteny
* @version $ID: MyAPP 2018-06-30 13:17 All rights reserved.$
*/
public class MyAPP {
public static void main(String[] args) {
System.out.println("starting app main tread, am i daemon " + Thread.currentThread().isDaemon());
// startDaemonThread();
startUserThread();
System.out.println("stop app main thread ");
// System.exit(0);
}
private static void startDaemonThread() {
Thread app = new Thread(() -> {
System.out.println("I am daemon thread, i started");
try {
while (true) {
Thread.sleep(100000);
System.out.println(LocalDateTime.now());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I am daemon thread, i am dead!!!");
});
app.setDaemon(true);
app.start();
}
private static void startUserThread() {
new Thread(() -> {
System.out.println("I am user thread, i started");
try {
while (true) {
System.out.println(LocalDateTime.now());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I am user thread, i am dead!!!");
}).start();
}
}