最近在调研一个分布式分片的任务调度框架的事情, 接触到了 Xxl-job, 在运行这个项目的服务案例时, 作者提供了一个spring-web形式的案例工程, clone到本地编译运行确实没有问题, 同时也部署了中心化管理服务Xxl-job-admin, 还用手动即时触发的方式调度了执行器服务中的任务逻辑;
然后在查阅项目文档时, 作者说明了执行器服务内部是启动了Jetty服务器与调度中心进行通信, 这里就有疑惑了, 既然是内部Jetty通信的方式, 那为何还要以servlet-web项目的形式部署到tomcat容器中呢?直接打包执行器项目为jar, 以普通java应用的方式启动不是更方便吗
通常我们运行一个java程序, 是从一个类的main方法为入口。背后就是JVM启动一个独立的非守护线程(non-daemon), 去执行我们的 static main 方法, 当出现以下情况, 应用程序线程就会被JVM关闭结束掉;
再来说说上面, 我想通过直接执行一个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"
classpath*:xxl-job-executor.properties
然后运行发行, 应用程序执行完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 {
private static final AtomicInteger flag = new AtomicInteger(1);
private static final AtomicBoolean awaited = new AtomicBoolean(false);
public static void main(String[] args) {
System.out.println("starting app main tread, am i daemon " + Thread.currentThread().isDaemon());
// startDaemonThread();
startUserThread();
startUserThreadLock();
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();
}
private static void startUserThreadLock() {
new Thread(() -> {
System.out.println("I am user thread, i started");
synchronized (flag) {
System.out.println(" [Dubbo] Current Spring Boot Application is await...");
while (!awaited.get()) {
try {
flag.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
System.out.println("I am user thread, i am dead!!!");
}).start();
}
}