Mat*_*e.N 4 java spring future spring-data-jpa taskscheduler
我正在使用Spring TaskScheduler在应用程序启动时安排任务(显然是……)。
TaskScheduler在我的SpringConfig中创建:
@Configuration
@EnableTransactionManagement
public class SpringConfig {
@Bean
public TaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
Run Code Online (Sandbox Code Playgroud)
spring boot应用程序在我的Main.class中启动,并安排任务@PostConstruct
@SpringBootApplication
@ComponentScan("...")
@EntityScan("...")
@EnableJpaRepositories("... .repositories")
@EnableAutoConfiguration
@PropertySources(value = {@PropertySource("classpath:application.properties")})
public class Main {
private final static Logger LOGGER = LoggerFactory.getLogger(Main.class);
private static SpringApplication application = new SpringApplication(Main.class);
private TaskScheduler taskScheduler;
private AnalysisCleaningThread cleaningThread;
@Inject
public void setCleaningThread(AnalysisCleaningThread cleaningThread) {
this.cleaningThread = cleaningThread;
}
@Inject
public void setTaskScheduler(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
public static void main(String[] args)
throws Exception {
try {
//Do some setup
application.run(args);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
@PostConstruct
public void init()
throws Exception {
//Do some setup as well
ScheduledFuture scheduledFuture = null;
LOGGER.info("********** Scheduling one time Cleaning Thread. Starting in 5 seconds **********");
Date nowPlus5Seconds = Date.from(LocalTime.now().plus(5, ChronoUnit.SECONDS).atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant());
scheduledFuture = this.taskScheduler.schedule(this.cleaningThread, nowPlus5Seconds);
while (true) {
//Somehow blocks thread from running
if (scheduledFuture.isDone()) {
break;
}
Thread.sleep(2000);
}
//schedule next periodic thread
}
Run Code Online (Sandbox Code Playgroud)
应用程序必须等待线程完成,因为其任务是在应用程序意外关闭后清除脏的数据库条目。下一个任务是拾取清理的条目并再次处理它们。清洁线程的实现方式如下:
@Named
@Singleton
public class AnalysisCleaningThread implements Runnable {
private static Logger LOGGER = LoggerFactory.getLogger(AnalysisCleaningThread.class);
private AnalysisService analysisService;
@Inject
public void setAnalysisService(AnalysisService analysisService) {
this.analysisService = analysisService;
}
@Override
public void run() {
List<Analysis> dirtyAnalyses = analysisService.findAllDirtyAnalyses();
if(dirtyAnalyses != null && dirtyAnalyses.size() > 0) {
LOGGER.info("Found " + dirtyAnalyses.size() + " dirty analyses. Cleaning... ");
for (Analysis currentAnalysis : dirtyAnalyses) {
//Reset AnalysisState so it is picked up by ProcessingThread on next run
currentAnalysis.setAnalysisState(AnalysisState.CREATED);
}
analysisService.saveAll(dirtyAnalyses);
} else {
LOGGER.info("No dirty analyses found.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
我在run方法的第一行和第二行上设置了一个断点。如果我使用ScheduledFuture.get(),则调用第一行,然后调用JPA存储库方法,但它永远不会返回...它不会在控制台中生成查询...
如果我使用ScheduledFuture.isDone(),则根本不会调用run方法。
编辑:
所以我进一步研究了这个问题,这就是我发现它停止工作的地方:
synchronized(this.singletonObjects),应用程序暂停并且永不继续从我的角度来看,似乎this.singletonObjects正在使用中,因此线程无法以某种方式继续运行...
因此,自从出现此问题以来,我进行了很多研究,并最终找到了解决我这种罕见情况的方法。
我首先注意到的是,没有future.get(),AnalysisCleaningThread运行起来没有任何问题,但是运行方法花费了大约2秒的时间来执行第一行,因此我认为数据库之前在后台一定要进行一些操作终于可以打电话了。
我在最初的问题编辑中通过调试发现,该应用程序在第93行synchronized(this.singletonObjects)的DefaultSingletonBeanRegistry.getSingleton方法中的同步块处停止,因此某些东西必须持有该锁定对象。实际上,一旦迭代方法调用将“ main”作为参数“ beanName”传递给,它实际上就停止在该行。DefaultSingletonBeanRegistry.getSingletongetSingleton
顺便说一下,调用该方法(或更好的方法链)是为了获得PlatformTransactionManager bean的实例来进行该服务(数据库)调用。
那时我的第一个想法是,这一定是一个僵局。
据我了解,bean仍在其生命周期中尚未准备就绪(仍在其@PostConstruct init()方法中)。在spring尝试获取平台事务管理器的实例以进行数据库查询时,应用程序陷入僵局。它实际上陷入了僵局,因为在遍历所有bean名称以查找PlatformTansactionManager时,它还试图解析由于@PostConstruct方法中的future.get()而正在等待的“主” bean。因此,它无法获取实例,并且一直在等待释放锁。
由于不想将代码放在另一个类中,因为Main.class是我的入口点,因此我开始寻找一个挂钩,该挂钩可在应用程序完全启动后启动任务。
我绊了一下@EventListener,在我的情况下ApplicationReadyEvent.class,它监听并运行,它起作用了。这是我的代码解决方案。
@SpringBootApplication
@ComponentScan("de. ... .analysis")
@EntityScan("de. ... .persistence")
@EnableJpaRepositories("de. ... .persistence.repositories")
@EnableAutoConfiguration
@PropertySources(value = {@PropertySource("classpath:application.properties")})
public class Main {
private final static Logger LOGGER = LoggerFactory.getLogger(Main.class);
private static SpringApplication application = new SpringApplication(Main.class);
private TaskScheduler taskScheduler;
private AnalysisProcessingThread processingThread;
private AnalysisCleaningThread cleaningThread;
@Inject
public void setProcessingThread(AnalysisProcessingThread processingThread) {
this.processingThread = processingThread;
}
@Inject
public void setCleaningThread(AnalysisCleaningThread cleaningThread) {
this.cleaningThread = cleaningThread;
}
@Inject
public void setTaskScheduler(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
public static void main(String[] args)
throws Exception {
try {
//Do some setup
application.run(args);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
@PostConstruct
public void init() throws Exception {
//Do some other setup
}
@EventListener(ApplicationReadyEvent.class)
public void startAndScheduleTasks() {
ScheduledFuture scheduledFuture = null;
LOGGER.info("********** Scheduling one time Cleaning Thread. Starting in 5 seconds **********");
Date nowPlus5Seconds = Date.from(LocalTime.now().plus(5, ChronoUnit.SECONDS).atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant());
scheduledFuture = this.taskScheduler.schedule(this.cleaningThread, nowPlus5Seconds);
try {
scheduledFuture.get();
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("********** Cleaning Thread did not finish as expected! Stopping thread. Dirty analyses may still remain in database **********", e);
scheduledFuture.cancel(true);
}
}
}
Run Code Online (Sandbox Code Playgroud)
@PostConstruct如果@PostConstruct在Spring可以获取PlatformTransactionManagerbean来执行Spring数据存储库查询之前,注释的方法没有结束,则从方法执行Spring数据存储库调用可能会导致死锁。它是一个无限循环还是一个future.get()方法...都没有关系。仅当迭代所有已注册beanName并最终调用DefaultSingletonBeanRegistry.getSingleton以找到PlatformTransactionManager bean的@PostConstruct方法使用当前在该方法中的bean名称调用getSingleton时,才会发生这种情况。如果在此PlatformTransactionManager之前找到了,那就不会发生。
| 归档时间: |
|
| 查看次数: |
725 次 |
| 最近记录: |