运行 多个线程(每个线程都有自己的应用程序上下文)并正常关闭
run multiple threads (each with own application context) and shutdown gracefully
我有一个多线程应用程序,我正在使用 SpringBoot 1.5 对其进行重新设计。请看下面的例子:
@Service
@Lazy
class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
private String account;
private boolean stopped = false;
private boolean processing;
public MyService(String account) {
logger.debug("MyService constructor");
this.account = account;
}
public void run() {
logger.debug("starting thread " + account);
while(!stopped) {
try {
processing = false;
Thread.sleep(5000); // awaiting some service response
processing = true;
Thread.sleep(3000); // processing service response
} catch (InterruptedException e) {
logger.error(null,e);
}
}
logger.debug("finished gracefully");
}
public void stop() {
stopped = true;
}
}
@SpringBootApplication
public class App {
private static final String[] accounts = { "user1", "user2", "user3" };
public static void main(String[] args) {
for(String account : accounts) {
new Thread(() -> {
ConfigurableApplicationContext context = SpringApplication.run(App.class, account);
BeanFactory factory = context.getBeanFactory();
MyService service = factory.getBean(MyService.class, account);
context.addApplicationListener(event -> {
if(event instanceof ContextClosedEvent) {
service.stop();
// context.registerShutdownHook();
// context.close();
}
});
service.run();
}).start();
}
}
}
application.properties
logging.level.com.example = DEBUG
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>multicontext-app</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
我想到了多上下文配置,因为我想自动装配 "singleton" 具有线程特定数据的作用域 bean。
问题:
- 这是为每个线程创建应用程序上下文的正确方法吗?
- 为什么我会看到重复的日志消息(线程数的平方倍)?例如,当 3 个线程 运行 时,"MyService constructor" 消息被打印 9 次,而不是 3 次(每个上下文一个实例)。
- 如何优雅地关闭每个服务线程,考虑到如果服务正在等待响应而不是处理它则无需等待?目前,当应用程序停止时,我看不到 "finished gracefully" 消息。
- 我是否需要致电
context.close()
或 context.registerShutdownHook()
或两者?我什么时候应该这样做,如果我不这样做会发生什么?
有多种方法可以实现正常关机,我将通过使用 Scheduled 技巧(我从 Whosebug 的某个地方学到的)来展示一种方法。
Scheduled 技巧在应用程序上下文启动时启动任务,但该任务永远不会再次调度 (initialDelay = 0L, fixedDelay = Long.MAX_VALUE
),有效地将调度任务转变为后台服务。 Spring 安排任务,您可以配置 Spring 如何通过 SchedulingConfigurer
处理计划任务,这让您可以控制计划任务,包括关机。
停止 运行 任务的正常方式是中断它们,所以我用它来停止服务。但如果确实需要,您仍然可以使用 ContextClosedEvent
停止来自 运行 的服务。
由于计划任务是异步启动的,因此不再需要在 main 方法中的单独线程中启动应用程序上下文。
我在命令行上使用以下命令进行了测试:
mvn clean spring-boot:run
和
mvn clean package spring-boot:repackage
java -jar target\[app].jar
按"ctrl-c"开始关机。
@SpringBootApplication
@EnableScheduling
public class App implements SchedulingConfigurer {
private static final Logger log = LoggerFactory.getLogger(App.class);
private static final AtomicInteger ctxCount = new AtomicInteger();
private static final String[] accounts = { "user1", "user2", "user3" };
public static void main(String[] args) {
Arrays.stream(accounts).forEach(account -> {
ConfigurableApplicationContext context = SpringApplication.run(App.class, account);
context.getBeanFactory().getBean(MyService.class, account);
});
}
@Bean(destroyMethod="shutdown")
public Executor taskScheduler() {
// https://github.com/spring-projects/spring-boot/issues/7779
final String prefix = "app-" + ctxCount.incrementAndGet() + "-mcsch-";
log.debug("Creating task scheduler {}", prefix);
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(1);
scheduler.setThreadNamePrefix(prefix);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(20);
return scheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
log.debug("Setting task scheduler.");
taskRegistrar.setScheduler(taskScheduler());
}
@Service
@Lazy
static class MyService {
private String account;
public MyService(String account) {
log.debug("{} - MyService constructor", account);
this.account = account;
}
// trick to "run at startup"
@Scheduled(initialDelay = 0L, fixedDelay = Long.MAX_VALUE)
public void run() {
boolean stopped = false;
while(!stopped) {
try {
log.debug("{} - sleeping", account);
Thread.sleep(5000);
} catch (InterruptedException e) {
log.debug("{} - sleep interrupted", account);
stopped = true;
}
}
log.debug("{} - finished gracefully", account);
}
} // MyService
}
我有一个多线程应用程序,我正在使用 SpringBoot 1.5 对其进行重新设计。请看下面的例子:
@Service
@Lazy
class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
private String account;
private boolean stopped = false;
private boolean processing;
public MyService(String account) {
logger.debug("MyService constructor");
this.account = account;
}
public void run() {
logger.debug("starting thread " + account);
while(!stopped) {
try {
processing = false;
Thread.sleep(5000); // awaiting some service response
processing = true;
Thread.sleep(3000); // processing service response
} catch (InterruptedException e) {
logger.error(null,e);
}
}
logger.debug("finished gracefully");
}
public void stop() {
stopped = true;
}
}
@SpringBootApplication
public class App {
private static final String[] accounts = { "user1", "user2", "user3" };
public static void main(String[] args) {
for(String account : accounts) {
new Thread(() -> {
ConfigurableApplicationContext context = SpringApplication.run(App.class, account);
BeanFactory factory = context.getBeanFactory();
MyService service = factory.getBean(MyService.class, account);
context.addApplicationListener(event -> {
if(event instanceof ContextClosedEvent) {
service.stop();
// context.registerShutdownHook();
// context.close();
}
});
service.run();
}).start();
}
}
}
application.properties
logging.level.com.example = DEBUG
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>multicontext-app</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
我想到了多上下文配置,因为我想自动装配 "singleton" 具有线程特定数据的作用域 bean。
问题:
- 这是为每个线程创建应用程序上下文的正确方法吗?
- 为什么我会看到重复的日志消息(线程数的平方倍)?例如,当 3 个线程 运行 时,"MyService constructor" 消息被打印 9 次,而不是 3 次(每个上下文一个实例)。
- 如何优雅地关闭每个服务线程,考虑到如果服务正在等待响应而不是处理它则无需等待?目前,当应用程序停止时,我看不到 "finished gracefully" 消息。
- 我是否需要致电
context.close()
或context.registerShutdownHook()
或两者?我什么时候应该这样做,如果我不这样做会发生什么?
有多种方法可以实现正常关机,我将通过使用 Scheduled 技巧(我从 Whosebug 的某个地方学到的)来展示一种方法。
Scheduled 技巧在应用程序上下文启动时启动任务,但该任务永远不会再次调度 (initialDelay = 0L, fixedDelay = Long.MAX_VALUE
),有效地将调度任务转变为后台服务。 Spring 安排任务,您可以配置 Spring 如何通过 SchedulingConfigurer
处理计划任务,这让您可以控制计划任务,包括关机。
停止 运行 任务的正常方式是中断它们,所以我用它来停止服务。但如果确实需要,您仍然可以使用 ContextClosedEvent
停止来自 运行 的服务。
由于计划任务是异步启动的,因此不再需要在 main 方法中的单独线程中启动应用程序上下文。
我在命令行上使用以下命令进行了测试:
mvn clean spring-boot:run
和
mvn clean package spring-boot:repackage
java -jar target\[app].jar
按"ctrl-c"开始关机。
@SpringBootApplication
@EnableScheduling
public class App implements SchedulingConfigurer {
private static final Logger log = LoggerFactory.getLogger(App.class);
private static final AtomicInteger ctxCount = new AtomicInteger();
private static final String[] accounts = { "user1", "user2", "user3" };
public static void main(String[] args) {
Arrays.stream(accounts).forEach(account -> {
ConfigurableApplicationContext context = SpringApplication.run(App.class, account);
context.getBeanFactory().getBean(MyService.class, account);
});
}
@Bean(destroyMethod="shutdown")
public Executor taskScheduler() {
// https://github.com/spring-projects/spring-boot/issues/7779
final String prefix = "app-" + ctxCount.incrementAndGet() + "-mcsch-";
log.debug("Creating task scheduler {}", prefix);
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(1);
scheduler.setThreadNamePrefix(prefix);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(20);
return scheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
log.debug("Setting task scheduler.");
taskRegistrar.setScheduler(taskScheduler());
}
@Service
@Lazy
static class MyService {
private String account;
public MyService(String account) {
log.debug("{} - MyService constructor", account);
this.account = account;
}
// trick to "run at startup"
@Scheduled(initialDelay = 0L, fixedDelay = Long.MAX_VALUE)
public void run() {
boolean stopped = false;
while(!stopped) {
try {
log.debug("{} - sleeping", account);
Thread.sleep(5000);
} catch (InterruptedException e) {
log.debug("{} - sleep interrupted", account);
stopped = true;
}
}
log.debug("{} - finished gracefully", account);
}
} // MyService
}