Thread starting during runtime shutdown问题的解决

探讨了在Android 5.0及以上版本中使用UncaughtExceptionHandler处理异常时遇到的问题,特别是当开启线程可能导致的java.lang.InternalError异常,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

安卓5.0及以后的系统 在 UncaughtExceptionHandler 方法中 开启线程 有可能会 使线程中的  Runnable 不执行,原因据说uncaughtException方法执行完成后ART环境就shutdown了


真正原因如下:

调用网络发送部分的策略是启动一个新的Service组件,读取持久化存储的数据,然后发送到服务端。至于为什么不直接开一个新的thread调用这个发送操作是有原因的。

  • Android 4.4.4 默认的http client库是基于okhttp的,在获取HTTP返回信息的时候为了防止阻塞会开新的thread。
  • Android 6.0使用了ART替代Dalvik开始,在shutdown的过程中不能开新的thread,否则又会跑出异常java.lang.InternalError: Thread starting during runtime shutdown
因此ACRA的解决办法就是开新的process的Service调用http,这个解决方案的讨论过程见:

那么bugly 在5.0之后的系统也能正常使用,可能也是开启了新的上报进程来处理的。所以网上流传的那一套安卓UncaughtExceptionHandler 机制已经不再适用了。fuck


可参考:https://my.oschina.net/azard/blog/614843


什么是 ACRA? 请移步这里, 应该是一个 国外版的专注于Android APP的 bugly系统

https://github.com/ACRA/acra


===================================================================================================

问题描述与分析:

  为了获取Crash日志,项目中实现了UncaughtExceptionHandler接口对未知异常进行捕获并上传到服务器中,同时停止App运行。在Android5.0以下系统一直未出现过问题,但是突然发现在Android 5.0以上的机器中这段代码出现了java.lang.InternalError: Thread starting during runtime shutdown(更多异常信息见最后部分的附录),在stackoverflow中得到了一些帮助:这个问题是因为线程开启的太晚了!

  什么时候开启线程才算得上“太晚了”呢? 在我的项目中UncaughtExceptionHandler实现类捕获到异常时(也就是UncaughtExceptionHandler接口的uncaughtException方法执行时),开启了一个线程用于上传崩溃日志,而在线程中上传崩溃日志时创建了HttpClient发送网络请求。HttpClient创建时设置了ThreadSafeClientConnManager来管理连接,而在ThreadSafeClientConnManager的源码中又一次开启了线程!也就是说,uncaughtException方法执行时在开启的线程中又开启了新的线程,这样可能会导致uncaughtException方法在执行完成时,“线程中的线程”才start(),而据说uncaughtException方法执行完成后ART环境就shutdown了(uncaughtException方法结束时ART是否会真的shutdown,这一点有待考证,笔者也仅是从其他非官方资料中得知),这种情况下就会抛出java.lang.InternalError: Thread starting during runtime shutdown。

  为了验证这个问题,编写了一段简单的代码:实现UncaughtExceptionHandler的uncaughtException方法,在uncaughtException方法中开启一个线程,在这个线程中再开一线程,同时在MainActivity中故意制造一个异常发生,此时果然会出现java.lang.InternalError: Thread starting during runtime shutdown(并不是每次都出现,因为如果uncaughtException方法执行结束之前,两个线程都完成start(),此问题就不会出现)。

 

解决方法:

  避免在uncaughtException方法中出现线程嵌套:针对我的项目,不再去在线程中创建HttpClient(因为HttpClient创建时还会开启线程,这就造成了线程中再次开启线程),而是先创建HttpClient对象,再在线程中使用HttpClient对象。这样就可以确保uncaughtException方法结束之前HttpClient已经被创建,HttpClient中开启线程也就不会有问题。

 

附异常信息:

java.lang.InternalError: Thread starting during runtime shutdown at java.lang.Thread.nativeCreate(Native Method) at java.lang.Thread.start(Thread.java:1042) at org.apache.http.impl.conn.tsccm.AbstractConnPool.enableConnectionGC(AbstractConnPool.java:140) at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager.createConnectionPool(ThreadSafeClientConnManager.java:120) at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager.<init>(ThreadSafeClientConnManager.java:98) at

public boolean adoptSessionInfo(SessionInfo session) { if (session.basicInfoSync()) { if (isTerminating) { return false; // 应用正在关闭,拒绝新会话 } SessionInfo old = null; String clientId = session.getClientId();//basicInfoSync之后就有clientId了 ALog.i("-----------client Id is:"+clientId); synchronized(sessions) { for (SessionInfo tmp : sessions) { if (clientId.equals(tmp.getClientId())) { old = tmp; break; } } if (old != null) { sessions.remove(old); } sessions.add(session); } if (old != null) { old.cleanUp(); } startProtocolThread(session); return true; } return false; } 报错08-07 14:59:08.943 3031 4106 I stbserver-com.taixin.stbserver.SessionInfo: [basicInfoSync:338]send basic info sync:connection {"volume":0,"areacode":"","boxno":"94810100916","dtv_type":"-1","program_version":"1.0","area_name":"","boxshortno":"886702996","boxmodel":"T70","protocolVersion":"v002.000.001","is_support_tv_prepared_status":"true","is_support_muti_code_rate":"true","account":"143606","name":"box94810100916","phonemac":"bab8c8e0d7d4","natype":-1,"boxVersionCode":6094} 08-07 14:59:08.943 3031 4106 I stbserver-com.taixin.stbserver.ProtocolHub: [adoptSessionInfo:134]-----------client Id is:andbab8c8e0d7d4 08-07 14:59:08.944 3031 4106 W System.err: java.lang.InternalError: Thread starting during runtime shutdown 08-07 14:59:08.944 3031 4106 W System.err: at java.lang.Thread.nativeCreate(Native Method) 08-07 14:59:08.944 3031 4106 W System.err: at java.lang.Thread.start(Thread.java:730) 08-07 14:59:08.945 3031 4106 W System.err: at com.taixin.stbserver.ProtocolHub.startProtocolThread(ProtocolHub.java:213) 08-07 14:59:08.945 3031 4106 W System.err: at com.taixin.stbserver.ProtocolHub.adoptSessionInfo(ProtocolHub.java:150) 08-07 14:59:08.945 3031 4106 W System.err: at com.taixin.stbserver.LanConnection$1.run(LanConnection.java:71) 08-07 14:59:08.945 3031 4106 W System.err: at java.lang.Thread.run(Thread.java:761) 08-07 14:59:08.945 3031 4106 I stbserver-LiveStreamManager: term
最新发布
08-08
/* * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.scheduling.quartz; import java.io.IOException; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import javax.sql.DataSource; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.impl.RemoteScheduler; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.StdSchedulerFactory; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.JobFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.SmartLifecycle; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.lang.Nullable; import org.springframework.scheduling.SchedulingException; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * {@link FactoryBean} that creates and configures a Quartz {@link org.quartz.Scheduler}, * manages its lifecycle as part of the Spring application context, and exposes the * Scheduler as bean reference for dependency injection. * * <p>Allows registration of JobDetails, Calendars and Triggers, automatically * starting the scheduler on initialization and shutting it down on destruction. * In scenarios that just require static registration of jobs at startup, there * is no need to access the Scheduler instance itself in application code. * * <p>For dynamic registration of jobs at runtime, use a bean reference to * this SchedulerFactoryBean to get direct access to the Quartz Scheduler * ({@code org.quartz.Scheduler}). This allows you to create new jobs * and triggers, and also to control and monitor the entire Scheduler. * * <p>Note that Quartz instantiates a new Job for each execution, in * contrast to Timer which uses a TimerTask instance that is shared * between repeated executions. Just JobDetail descriptors are shared. * * <p>When using persistent jobs, it is strongly recommended to perform all * operations on the Scheduler within Spring-managed (or plain JTA) transactions. * Else, database locking will not properly work and might even break. * (See {@link #setDataSource setDataSource} javadoc for details.) * * <p>The preferred way to achieve transactional execution is to demarcate * declarative transactions at the business facade level, which will * automatically apply to Scheduler operations performed within those scopes. * Alternatively, you may add transactional advice for the Scheduler itself. * * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1. * * @author Juergen Hoeller * @since 18.02.2004 * @see #setDataSource * @see org.quartz.Scheduler * @see org.quartz.SchedulerFactory * @see org.quartz.impl.StdSchedulerFactory * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean */ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle { /** * The thread count property. */ public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount"; /** * The default thread count. */ public static final int DEFAULT_THREAD_COUNT = 10; private static final ThreadLocal<ResourceLoader> configTimeResourceLoaderHolder = new ThreadLocal<>(); private static final ThreadLocal<Executor> configTimeTaskExecutorHolder = new ThreadLocal<>(); private static final ThreadLocal<DataSource> configTimeDataSourceHolder = new ThreadLocal<>(); private static final ThreadLocal<DataSource> configTimeNonTransactionalDataSourceHolder = new ThreadLocal<>(); /** * Return the {@link ResourceLoader} for the currently configured Quartz Scheduler, * to be used by {@link ResourceLoaderClassLoadHelper}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @see #setApplicationContext * @see ResourceLoaderClassLoadHelper */ @Nullable public static ResourceLoader getConfigTimeResourceLoader() { return configTimeResourceLoaderHolder.get(); } /** * Return the {@link Executor} for the currently configured Quartz Scheduler, * to be used by {@link LocalTaskExecutorThreadPool}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 2.0 * @see #setTaskExecutor * @see LocalTaskExecutorThreadPool */ @Nullable public static Executor getConfigTimeTaskExecutor() { return configTimeTaskExecutorHolder.get(); } /** * Return the {@link DataSource} for the currently configured Quartz Scheduler, * to be used by {@link LocalDataSourceJobStore}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ @Nullable public static DataSource getConfigTimeDataSource() { return configTimeDataSourceHolder.get(); } /** * Return the non-transactional {@link DataSource} for the currently configured * Quartz Scheduler, to be used by {@link LocalDataSourceJobStore}. * <p>This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 1.1 * @see #setNonTransactionalDataSource * @see LocalDataSourceJobStore */ @Nullable public static DataSource getConfigTimeNonTransactionalDataSource() { return configTimeNonTransactionalDataSourceHolder.get(); } @Nullable private SchedulerFactory schedulerFactory; private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class; @Nullable private String schedulerName; @Nullable private Resource configLocation; @Nullable private Properties quartzProperties; @Nullable private Executor taskExecutor; @Nullable private DataSource dataSource; @Nullable private DataSource nonTransactionalDataSource; @Nullable private Map<String, ?> schedulerContextMap; @Nullable private String applicationContextSchedulerContextKey; @Nullable private JobFactory jobFactory; private boolean jobFactorySet = false; private boolean autoStartup = true; private int startupDelay = 0; private int phase = DEFAULT_PHASE; private boolean exposeSchedulerInRepository = false; private boolean waitForJobsToCompleteOnShutdown = false; @Nullable private String beanName; @Nullable private ApplicationContext applicationContext; @Nullable private Scheduler scheduler; /** * Set an external Quartz {@link SchedulerFactory} instance to use. * <p>Default is an internal {@link StdSchedulerFactory} instance. If this method is * called, it overrides any class specified through {@link #setSchedulerFactoryClass} * as well as any settings specified through {@link #setConfigLocation}, * {@link #setQuartzProperties}, {@link #setTaskExecutor} or {@link #setDataSource}. * <p><b>NOTE:</b> With an externally provided {@code SchedulerFactory} instance, * local settings such as {@link #setConfigLocation} or {@link #setQuartzProperties} * will be ignored here in {@code SchedulerFactoryBean}, expecting the external * {@code SchedulerFactory} instance to get initialized on its own. * @since 4.3.15 * @see #setSchedulerFactoryClass */ public void setSchedulerFactory(SchedulerFactory schedulerFactory) { this.schedulerFactory = schedulerFactory; } /** * Set the Quartz {@link SchedulerFactory} implementation to use. * <p>Default is the {@link StdSchedulerFactory} class, reading in the standard * {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz * properties, specify {@link #setConfigLocation "configLocation"} and/or * {@link #setQuartzProperties "quartzProperties"} etc on this local * {@code SchedulerFactoryBean} instance. * @see org.quartz.impl.StdSchedulerFactory * @see #setConfigLocation * @see #setQuartzProperties * @see #setTaskExecutor * @see #setDataSource */ public void setSchedulerFactoryClass(Class<? extends SchedulerFactory> schedulerFactoryClass) { this.schedulerFactoryClass = schedulerFactoryClass; } /** * Set the name of the Scheduler to create via the SchedulerFactory, as an * alternative to the {@code org.quartz.scheduler.instanceName} property. * <p>If not specified, the name will be taken from Quartz properties * ({@code org.quartz.scheduler.instanceName}), or from the declared * {@code SchedulerFactoryBean} bean name as a fallback. * @see #setBeanName * @see StdSchedulerFactory#PROP_SCHED_INSTANCE_NAME * @see org.quartz.SchedulerFactory#getScheduler() * @see org.quartz.SchedulerFactory#getScheduler(String) */ public void setSchedulerName(String schedulerName) { this.schedulerName = schedulerName; } /** * Set the location of the Quartz properties config file, for example * as classpath resource "classpath:quartz.properties". * <p>Note: Can be omitted when all necessary properties are specified * locally via this bean, or when relying on Quartz' default configuration. * @see #setQuartzProperties */ public void setConfigLocation(Resource configLocation) { this.configLocation = configLocation; } /** * Set Quartz properties, like "org.quartz.threadPool.class". * <p>Can be used to override values in a Quartz properties config file, * or to specify all necessary properties locally. * @see #setConfigLocation */ public void setQuartzProperties(Properties quartzProperties) { this.quartzProperties = quartzProperties; } /** * Set a Spring-managed {@link Executor} to use as Quartz backend. * Exposed as thread pool through the Quartz SPI. * <p>Can be used to assign a local JDK ThreadPoolExecutor or a CommonJ * WorkManager as Quartz backend, to avoid Quartz's manual thread creation. * <p>By default, a Quartz SimpleThreadPool will be used, configured through * the corresponding Quartz properties. * @since 2.0 * @see #setQuartzProperties * @see LocalTaskExecutorThreadPool * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor * @see org.springframework.scheduling.concurrent.DefaultManagedTaskExecutor */ public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = taskExecutor; } /** * Set the default {@link DataSource} to be used by the Scheduler. * <p>Note: If this is set, the Quartz settings should not define * a job store "dataSource" to avoid meaningless double configuration. * Also, do not define a "org.quartz.jobStore.class" property at all. * (You may explicitly define Spring's {@link LocalDataSourceJobStore} * but that's the default when using this method anyway.) * <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used. * It is therefore strongly recommended to perform all operations on * the Scheduler within Spring-managed (or plain JTA) transactions. * Else, database locking will not properly work and might even break * (for example, if trying to obtain a lock on Oracle without a transaction). * <p>Supports both transactional and non-transactional DataSource access. * With a non-XA DataSource and local Spring transactions, a single DataSource * argument is sufficient. In case of an XA DataSource and global JTA transactions, * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set, * passing in a non-XA DataSource that will not participate in global transactions. * @since 1.1 * @see #setNonTransactionalDataSource * @see #setQuartzProperties * @see #setTransactionManager * @see LocalDataSourceJobStore */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Set the {@link DataSource} to be used <i>for non-transactional access</i>. * <p>This is only necessary if the default DataSource is an XA DataSource that will * always participate in transactions: A non-XA version of that DataSource should * be specified as "nonTransactionalDataSource" in such a scenario. * <p>This is not relevant with a local DataSource instance and Spring transactions. * Specifying a single default DataSource as "dataSource" is sufficient there. * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) { this.nonTransactionalDataSource = nonTransactionalDataSource; } /** * Register objects in the Scheduler context via a given Map. * These objects will be available to any Job that runs in this Scheduler. * <p>Note: When using persistent Jobs whose JobDetail will be kept in the * database, do not put Spring-managed beans or an ApplicationContext * reference into the JobDataMap but rather into the SchedulerContext. * @param schedulerContextAsMap a Map with String keys and any objects as * values (for example Spring-managed beans) * @see JobDetailFactoryBean#setJobDataAsMap */ public void setSchedulerContextAsMap(Map<String, ?> schedulerContextAsMap) { this.schedulerContextMap = schedulerContextAsMap; } /** * Set the key of an {@link ApplicationContext} reference to expose in the * SchedulerContext, for example "applicationContext". Default is none. * Only applicable when running in a Spring ApplicationContext. * <p>Note: When using persistent Jobs whose JobDetail will be kept in the * database, do not put an ApplicationContext reference into the JobDataMap * but rather into the SchedulerContext. * <p>In case of a QuartzJobBean, the reference will be applied to the Job * instance as bean property. An "applicationContext" attribute will * correspond to a "setApplicationContext" method in that scenario. * <p>Note that BeanFactory callback interfaces like ApplicationContextAware * are not automatically applied to Quartz Job instances, because Quartz * itself is responsible for the lifecycle of its Jobs. * @see JobDetailFactoryBean#setApplicationContextJobDataKey * @see org.springframework.context.ApplicationContext */ public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) { this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey; } /** * Set the Quartz {@link JobFactory} to use for this Scheduler. * <p>Default is Spring's {@link AdaptableJobFactory}, which supports * {@link java.lang.Runnable} objects as well as standard Quartz * {@link org.quartz.Job} instances. Note that this default only applies * to a <i>local</i> Scheduler, not to a RemoteScheduler (where setting * a custom JobFactory is not supported by Quartz). * <p>Specify an instance of Spring's {@link SpringBeanJobFactory} here * (typically as an inner bean definition) to automatically populate a job's * bean properties from the specified job data map and scheduler context. * @since 2.0 * @see AdaptableJobFactory * @see SpringBeanJobFactory */ public void setJobFactory(JobFactory jobFactory) { this.jobFactory = jobFactory; this.jobFactorySet = true; } /** * Set whether to automatically start the scheduler after initialization. * <p>Default is "true"; set this to "false" to allow for manual startup. */ public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } /** * Return whether this scheduler is configured for auto-startup. If "true", * the scheduler will start after the context is refreshed and after the * start delay, if any. */ @Override public boolean isAutoStartup() { return this.autoStartup; } /** * Specify the phase in which this scheduler should be started and stopped. * The startup order proceeds from lowest to highest, and the shutdown order * is the reverse of that. By default this value is {@code Integer.MAX_VALUE} * meaning that this scheduler starts as late as possible and stops as soon * as possible. * @since 3.0 */ public void setPhase(int phase) { this.phase = phase; } /** * Return the phase in which this scheduler will be started and stopped. */ @Override public int getPhase() { return this.phase; } /** * Set the number of seconds to wait after initialization before * starting the scheduler asynchronously. Default is 0, meaning * immediate synchronous startup on initialization of this bean. * <p>Setting this to 10 or 20 seconds makes sense if no jobs * should be run before the entire application has started up. */ public void setStartupDelay(int startupDelay) { this.startupDelay = startupDelay; } /** * Set whether to expose the Spring-managed {@link Scheduler} instance in the * Quartz {@link SchedulerRepository}. Default is "false", since the Spring-managed * Scheduler is usually exclusively intended for access within the Spring context. * <p>Switch this flag to "true" in order to expose the Scheduler globally. * This is not recommended unless you have an existing Spring application that * relies on this behavior. Note that such global exposure was the accidental * default in earlier Spring versions; this has been fixed as of Spring 2.5.6. */ public void setExposeSchedulerInRepository(boolean exposeSchedulerInRepository) { this.exposeSchedulerInRepository = exposeSchedulerInRepository; } /** * Set whether to wait for running jobs to complete on shutdown. * <p>Default is "false". Switch this to "true" if you prefer * fully completed jobs at the expense of a longer shutdown phase. * @see org.quartz.Scheduler#shutdown(boolean) */ public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) { this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; } @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } //--------------------------------------------------------------------- // Implementation of InitializingBean interface //--------------------------------------------------------------------- @Override public void afterPropertiesSet() throws Exception { if (this.dataSource == null && this.nonTransactionalDataSource != null) { this.dataSource = this.nonTransactionalDataSource; } if (this.applicationContext != null && this.resourceLoader == null) { this.resourceLoader = this.applicationContext; } // Initialize the Scheduler instance... this.scheduler = prepareScheduler(prepareSchedulerFactory()); try { registerListeners(); registerJobsAndTriggers(); } catch (Exception ex) { try { this.scheduler.shutdown(true); } catch (Exception ex2) { logger.debug("Scheduler shutdown exception after registration failure", ex2); } throw ex; } } /** * Create a SchedulerFactory if necessary and apply locally defined Quartz properties to it. * @return the initialized SchedulerFactory */ private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException { SchedulerFactory schedulerFactory = this.schedulerFactory; if (schedulerFactory == null) { // Create local SchedulerFactory instance (typically a StdSchedulerFactory) schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass); if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) { initSchedulerFactory(stdSchedulerFactory); } else if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) { throw new IllegalArgumentException( "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory); } // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties) } // Otherwise, assume that externally provided factory has been initialized with appropriate settings return schedulerFactory; } /** * Initialize the given SchedulerFactory, applying locally defined Quartz properties to it. * @param schedulerFactory the SchedulerFactory to initialize */ private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException { Properties mergedProps = new Properties(); if (this.resourceLoader != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS, ResourceLoaderClassLoadHelper.class.getName()); } if (this.taskExecutor != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, LocalTaskExecutorThreadPool.class.getName()); } else { // Set necessary default properties here, as Quartz will not apply // its default configuration when explicitly given properties. mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT)); } if (this.configLocation != null) { if (logger.isDebugEnabled()) { logger.debug("Loading Quartz config from [" + this.configLocation + "]"); } PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation); } CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps); if (this.dataSource != null) { mergedProps.putIfAbsent(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName()); } // Determine scheduler name across local settings and Quartz properties... if (this.schedulerName != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName); } else { String nameProp = mergedProps.getProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME); if (nameProp != null) { this.schedulerName = nameProp; } else if (this.beanName != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.beanName); this.schedulerName = this.beanName; } } schedulerFactory.initialize(mergedProps); } private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException { if (this.resourceLoader != null) { // Make given ResourceLoader available for SchedulerFactory configuration. configTimeResourceLoaderHolder.set(this.resourceLoader); } if (this.taskExecutor != null) { // Make given TaskExecutor available for SchedulerFactory configuration. configTimeTaskExecutorHolder.set(this.taskExecutor); } if (this.dataSource != null) { // Make given DataSource available for SchedulerFactory configuration. configTimeDataSourceHolder.set(this.dataSource); } if (this.nonTransactionalDataSource != null) { // Make given non-transactional DataSource available for SchedulerFactory configuration. configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource); } // Get Scheduler instance from SchedulerFactory. try { Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName); populateSchedulerContext(scheduler); if (!this.jobFactorySet && !(scheduler instanceof RemoteScheduler)) { // Use AdaptableJobFactory as default for a local Scheduler, unless when // explicitly given a null value through the "jobFactory" bean property. this.jobFactory = new AdaptableJobFactory(); } if (this.jobFactory != null) { if (this.applicationContext != null && this.jobFactory instanceof ApplicationContextAware applicationContextAware) { applicationContextAware.setApplicationContext(this.applicationContext); } if (this.jobFactory instanceof SchedulerContextAware schedulerContextAware) { schedulerContextAware.setSchedulerContext(scheduler.getContext()); } scheduler.setJobFactory(this.jobFactory); } return scheduler; } finally { if (this.resourceLoader != null) { configTimeResourceLoaderHolder.remove(); } if (this.taskExecutor != null) { configTimeTaskExecutorHolder.remove(); } if (this.dataSource != null) { configTimeDataSourceHolder.remove(); } if (this.nonTransactionalDataSource != null) { configTimeNonTransactionalDataSourceHolder.remove(); } } } /** * Create the Scheduler instance for the given factory and scheduler name. * Called by {@link #afterPropertiesSet}. * <p>The default implementation invokes SchedulerFactory's {@code getScheduler} * method. Can be overridden for custom Scheduler creation. * @param schedulerFactory the factory to create the Scheduler with * @param schedulerName the name of the scheduler to create * @return the Scheduler instance * @throws SchedulerException if thrown by Quartz methods * @see #afterPropertiesSet * @see org.quartz.SchedulerFactory#getScheduler */ @SuppressWarnings("NullAway") protected Scheduler createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName) throws SchedulerException { // Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading. Thread currentThread = Thread.currentThread(); ClassLoader threadContextClassLoader = currentThread.getContextClassLoader(); boolean overrideClassLoader = (this.resourceLoader != null && this.resourceLoader.getClassLoader() != threadContextClassLoader); if (overrideClassLoader) { currentThread.setContextClassLoader(this.resourceLoader.getClassLoader()); } try { SchedulerRepository repository = SchedulerRepository.getInstance(); synchronized (repository) { Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null); Scheduler newScheduler = schedulerFactory.getScheduler(); if (newScheduler == existingScheduler) { throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " + "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!"); } if (!this.exposeSchedulerInRepository) { // Need to remove it in this case, since Quartz shares the Scheduler instance by default! SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName()); } return newScheduler; } } finally { if (overrideClassLoader) { // Reset original thread context ClassLoader. currentThread.setContextClassLoader(threadContextClassLoader); } } } /** * Expose the specified context attributes and/or the current * ApplicationContext in the Quartz SchedulerContext. */ private void populateSchedulerContext(Scheduler scheduler) throws SchedulerException { // Put specified objects into Scheduler context. if (this.schedulerContextMap != null) { scheduler.getContext().putAll(this.schedulerContextMap); } // Register ApplicationContext in Scheduler context. if (this.applicationContextSchedulerContextKey != null) { if (this.applicationContext == null) { throw new IllegalStateException( "SchedulerFactoryBean needs to be set up in an ApplicationContext " + "to be able to handle an 'applicationContextSchedulerContextKey'"); } scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext); } } /** * Start the Quartz Scheduler, respecting the "startupDelay" setting. * @param scheduler the Scheduler to start * @param startupDelay the number of seconds to wait before starting * the Scheduler asynchronously */ protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException { if (startupDelay <= 0) { logger.info("Starting Quartz Scheduler now"); scheduler.start(); } else { if (logger.isInfoEnabled()) { logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() + "] in " + startupDelay + " seconds"); } // Not using the Quartz startDelayed method since we explicitly want a daemon // thread here, not keeping the JVM alive in case of all other threads ending. Thread schedulerThread = new Thread(() -> { try { TimeUnit.SECONDS.sleep(startupDelay); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); // simply proceed } if (logger.isInfoEnabled()) { logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds"); } try { scheduler.start(); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler after delay", ex); } }); schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]"); schedulerThread.setDaemon(true); schedulerThread.start(); } } //--------------------------------------------------------------------- // Implementation of FactoryBean interface //--------------------------------------------------------------------- @Override public Scheduler getScheduler() { Assert.state(this.scheduler != null, "No Scheduler set"); return this.scheduler; } @Override @Nullable public Scheduler getObject() { return this.scheduler; } @Override public Class<? extends Scheduler> getObjectType() { return (this.scheduler != null ? this.scheduler.getClass() : Scheduler.class); } @Override public boolean isSingleton() { return true; } //--------------------------------------------------------------------- // Implementation of SmartLifecycle interface //--------------------------------------------------------------------- @Override public void start() throws SchedulingException { if (this.scheduler != null) { try { startScheduler(this.scheduler, this.startupDelay); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler", ex); } } } @Override public void stop() throws SchedulingException { if (this.scheduler != null) { try { this.scheduler.standby(); } catch (SchedulerException ex) { throw new SchedulingException("Could not stop Quartz Scheduler", ex); } } } @Override public boolean isRunning() throws SchedulingException { if (this.scheduler != null) { try { return !this.scheduler.isInStandbyMode(); } catch (SchedulerException ex) { return false; } } return false; } //--------------------------------------------------------------------- // Implementation of DisposableBean interface //--------------------------------------------------------------------- /** * Shut down the Quartz scheduler on bean factory shutdown, * stopping all scheduled jobs. */ @Override public void destroy() throws SchedulerException { if (this.scheduler != null) { logger.info("Shutting down Quartz Scheduler"); this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); } } } 我看到的start和你的不一样
07-26
<think>我们正在讨论Spring框架中`SchedulerFactoryBean`的`autoStartup`字段及其`start`方法。用户提到“我看到的start和你的不一样”,可能是指用户看到的源码中`start`方法的实现或调用方式与我的描述有差异。因此,我需要详细解释`autoStartup`的作用以及`start`方法的实现,并考虑用户可能遇到的不同情况。 根据引用[1]和[2],我们知道Spring Framework提供了对Quartz的集成支持,其中`SchedulerFactoryBean`是核心配置类。`autoStartup`字段控制调度器是否在应用上下文启动时自动启动。 ### 1. **`autoStartup`字段的作用** - **默认行为**:`autoStartup`默认为`true`,即当`SchedulerFactoryBean`初始化完成后(在`afterPropertiesSet()`方法中)会自动启动Quartz调度器。 - **设置为false**:如果设置为`false`,则不会自动启动,需要手动调用`start()`方法来启动调度器。 ### 2. **`autoStartup`的判断时机** 在`SchedulerFactoryBean`的初始化方法`afterPropertiesSet()`中,会检查`autoStartup`的值: ```java public void afterPropertiesSet() throws Exception { // ... 其他初始化代码(如创建Scheduler实例) if (this.autoStartup) { startScheduler(); } } ``` ### 3. **`startScheduler()`方法** `startScheduler()`方法内部会调用Quartz调度器的`start()`方法: ```java protected void startScheduler() throws SchedulerException { this.scheduler.start(); } ``` 这里的`scheduler`是Quartz的`org.quartz.Scheduler`实例。 ### 4. **用户提到的“start和你的不一样”** 用户可能在源码中看到以下情况之一: - **情况1**:用户可能看到的是`SchedulerFactoryBean`的`start()`方法,而不是`startScheduler()`。`SchedulerFactoryBean`提供了一个公共的`start()`方法,允许手动启动调度器(即使`autoStartup`为false也可以调用): ```java public void start() throws SchedulingException { if (this.scheduler != null) { try { this.scheduler.start(); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler", ex); } } } ``` 这个方法通常用于在`autoStartup=false`时手动启动。 - **情况2**:用户可能看到的是Spring Boot中自动配置的启动方式。在Spring Boot中,通过`QuartzAutoConfiguration`配置`SchedulerFactoryBean`,并且其`autoStartup`属性由配置项`spring.quartz.auto-startup`控制(默认为true)。但Spring Boot可能会在应用生命周期的不同阶段启动调度器。 - **情况3**:用户可能看到的是`SchedulerFactoryBean`的`start()`方法被重写或扩展了。例如,在自定义的`SchedulerFactoryBean`子类中,可能覆盖了`start()`方法或`startScheduler()`方法。 ### 5. **手动启动示例** 如果设置`autoStartup=false`,可以在需要的时候(如应用完全初始化后)手动启动: ```java @Autowired private SchedulerFactoryBean schedulerFactoryBean; public void startSchedulerManually() { schedulerFactoryBean.start(); } ``` ### 6. **在Spring Boot中覆盖`autoStartup`配置** 在Spring Boot中,可以通过`application.properties`设置: ```properties spring.quartz.auto-startup=false ``` 或者通过Java配置: ```java @Bean public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setDataSource(dataSource); factory.setAutoStartup(false); // 覆盖为false return factory; } ``` ### 7. **与用户提供的引用内容的关系** - 引用[3]和[4]提到了Quartz的Job配置,但未直接涉及`autoStartup`。不过,这些引用说明用户可能在配置Quartz任务时遇到了序列化问题(引用[3])或触发器配置(引用[4]),但当前问题聚焦于调度器的启动机制。 ### 总结 `autoStartup`字段控制`SchedulerFactoryBean`初始化时是否自动启动Quartz调度器。默认自动启动,可通过配置关闭。手动启动时调用`SchedulerFactoryBean`的`start()`方法。用户看到的“不一样”的`start`可能是指: 1. 直接调用了Quartz `Scheduler`的`start()`方法(但推荐通过`SchedulerFactoryBean`的`start()`方法)。 2. 在Spring Boot中使用了不同的自动配置机制。 建议用户检查他们查看的源码位置,并确认是否涉及自定义扩展或Spring Boot自动配置。 --- §§相关问题§§ 1. 在Spring Boot中如何通过配置文件控制Quartz调度器的自动启动? 2. 如何确保在Spring应用上下文完全初始化后再启动Quartz调度器? 3. 如何通过编程方式动态启动和停止Quartz调度器? 4. 在集群环境下,Quartz调度器的自动启动有什么注意事项?</think>### Spring Framework `SchedulerFactoryBean` 的 `autoStartup` 字段详解 #### 一、`autoStartup` 字段的核心作用 `autoStartup` 是 `SchedulerFactoryBean` 中控制 Quartz 调度器是否自动启动的关键参数: ```java public class SchedulerFactoryBean extends SchedulerAccessor { private boolean autoStartup = true; // 默认值为true } ``` [^1] **核心作用**: 1. **自动启动控制**(默认 `true`): - 当应用上下文启动时,自动初始化并启动 Quartz 调度器 - 所有关联的触发器(`Trigger`)会自动开始调度任务 2. **手动启动模式**(设置为 `false`): - 调度器初始化后处于暂停状态 - 需显式调用 `start()` 方法激活调度器 - 适用于需要延迟启动或条件触发的场景 #### 二、`start()` 方法的实现机制 当 `autoStartup=true` 时,在 Bean 初始化阶段会触发启动流程: ```java // 在 afterPropertiesSet() 方法中的关键调用 public void afterPropertiesSet() throws Exception { // ... 初始化Scheduler实例 if (this.autoStartup) { // 判断autoStartup标志 startScheduler(); // 启动调度器 } } // 启动方法的具体实现 protected void startScheduler() throws SchedulerException { this.scheduler.start(); // 调用Quartz原生API } ``` [^1] **方法执行流程**: 1. 通过 `afterPropertiesSet()` 在 Bean 初始化阶段触发 2. 检测 `autoStartup` 标志为 `true` 时调用 `startScheduler()` 3. 最终委托给 Quartz 的 `org.quartz.Scheduler.start()` 方法 4. 启动后调度器开始执行所有注册的触发器 #### 三、配置示例 **XML 配置方式**: ```xml <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="autoStartup" value="false"/> <!-- 禁止自动启动 --> <property name="triggers"> <list> <ref bean="cronTrigger"/> <!-- 引用触发器 --> </list> </property> </bean> ``` [^4] **Java 配置方式**: ```java @Bean public SchedulerFactoryBean schedulerFactory(Trigger... triggers) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setAutoStartup(false); // 手动控制启动 factory.setTriggers(triggers); return factory; } ``` #### 四、手动启动调度器 当 `autoStartup=false` 时,需编程式启动: ```java @Autowired private SchedulerFactoryBean schedulerFactory; public void startScheduler() { try { if (!schedulerFactory.getScheduler().isStarted()) { schedulerFactory.start(); // 显式启动 } } catch (SchedulerException e) { // 异常处理 } } ``` #### 五、特殊场景说明 1. **延迟启动需求**: ```java // 使用ApplicationListener确保依赖资源就绪 @Component public class SchedulerStarter implements ApplicationListener<ContextRefreshedEvent> { @Autowired private SchedulerFactoryBean scheduler; @Override public void onApplicationEvent(ContextRefreshedEvent event) { scheduler.start(); // 在上下文刷新完成后启动 } } ``` 2. **与 Spring Boot 的集成差异**: - Spring Boot 通过 `QuartzAutoConfiguration` 自动配置调度器 - 可通过 `application.properties` 覆盖行为: ```properties spring.quartz.auto-startup=false ``` 3. **集群环境注意事项**: - 在集群中需确保所有节点的 `autoStartup` 配置一致 - 建议配合 `spring.quartz.job-store-type=jdbc` 使用数据库存储[^3] > **最佳实践**:生产环境中建议设置 `autoStartup=false` 并通过生命周期事件手动启动,可避免因依赖资源未就绪导致的调度异常[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值