使用SpingCloud必然會用到Hystrix作熔斷降級,也必然會用到@HystrixCommand
註解,@HystrixCommand
註解能夠配置的除了經常使用的groupKey、commandKey、fallbackMethod等,還有一個很關鍵的就是threadPoolKey,就是使用Hystrix線程隔離策略時的線程池Keyjava
/** * This annotation used to specify some methods which should be processes as hystrix commands. */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface HystrixCommand { /** * The command group key is used for grouping together commands such as for reporting, * alerting, dashboards or team/library ownership. * <p/> * default => the runtime class name of annotated method * * @return group key */ String groupKey() default ""; /** * Hystrix command key. * <p/> * default => the name of annotated method. for example: * <code> * ... * @HystrixCommand * public User getUserById(...) * ... * the command name will be: 'getUserById' * </code> * * @return command key */ String commandKey() default ""; /** * The thread-pool key is used to represent a * HystrixThreadPool for monitoring, metrics publishing, caching and other such uses. * * @return thread pool key */ String threadPoolKey() default ""; ......省略 }
而使用中咱們經常只指定fallbackMethod回退方法,而不會指定全部屬性,從@HystrixCommand
的源碼註釋來看git
但threadPoolKey卻沒有說明默認值,而threadPoolKey是和執行HystrixCommand的線程池直接相關的github
因此個人疑問就是,threadPoolKey有默認值嗎? 默認值是什麼? 執行HystrixCommand的線程池又是怎麼初始化的? 能夠動態調整嗎?spring
spring-cloud-example-consumer-ribbon-hystrix-threadpoolchrome
首先須要啓動 spring-cloud-example-eureka-server-standalone註冊中心 和 spring-cloud-example-simple-provider服務提供者瀏覽器
再啓動 spring-cloud-example-consumer-ribbon-hystrix-threadpool緩存
測試端點併發
http://127.0.0.1:20006/testDefaultThreadPoolKeyapp
http://127.0.0.1:20006/testDefaultThreadPoolKey2dom
testDefaultThreadPoolKey 和 testDefaultThreadPoolKey2 是同一個service的兩個方法,分別使用@HystrixCommand指定fallback方法
線程池大小設置爲2,且不使用隊列暫存,服務提供方sleep 30秒,經過chrome多窗口調用 /testDefaultThreadPoolKey 端點
或同時調用 /testDefaultThreadPoolKey、/testDefaultThreadPoolKey2 端點
經過大於線程池最大值的請求被線程池拒絕進入fallback,判斷線程池是方法級,仍是類級的,以及threadPoolKey默認值
注意:
使用firefox瀏覽器測試有問題,多標籤頁必須等待一個GET請求完成,才能繼續下一個,測不出併發的效果。
一開始覺得是程序上的限制,後來才發現是瀏覽器,使用chrome問題解決
hystrix.threadpool.default.coreSize = 2 hystrix.threadpool.default.maximumSize = 2 hystrix.threadpool.default.maxQueueSize = -1
線程池的coreSize
和maximumSize
都設置爲2(1.5.9版本後才添加maximumSize
),且線程池隊列大小爲-1,即便用SynchronousQueue
Hystrix線程池的其它屬性: Hystrix Thread Pool Properties
chrome瀏覽器連續GET請求調用6次,分別調用3次 /testDefaultThreadPoolKey,3次 /testDefaultThreadPoolKey2,以綠色線問分隔,可見前兩次調用成功,後4次均直接拒絕,進入fallback
具體異常信息爲:
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6d72dcf rejected from java.util.concurrent.ThreadPoolExecutor@431bfebc[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 1]
線程池大小爲2起到了做用,將大於併發數的請求拒絕了,而且不管是隻調用 /testDefaultThreadPoolKey,仍是輪詢調用 /testDefaultThreadPoolKey 和 /testDefaultThreadPoolKey2 ,測試結果都是這樣。再根據hystrix線程的名字 hystrix-ConsumerRibbonHystrixThreadPoolService-n,能夠猜測:Hystrix的 threadPoolKey是和hystrixCommand執行的類相關的,可能一個類使用一個線程池,因此兩個service方法纔會共用線程池
首先,被@HystrixCommand註解標註的方法會被AOP攔截,具體邏輯在 HystrixCommandAspect
private static final Map<HystrixPointcutType, MetaHolderFactory> META_HOLDER_FACTORY_MAP; // 初始化用於處理 HystrixCommand 和 HystrixCollapser 的 MetaHolderFactory // HystrixCommand -- CommandMetaHolderFactory // HystrixCollapser -- CollapserMetaHolderFactory static { META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder() .put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory()) .put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory()) .build(); } // HystrixCommand Pointcut切入點 @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") public void hystrixCommandAnnotationPointcut() { } // HystrixCollapser Pointcut切入點 @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)") public void hystrixCollapserAnnotationPointcut() { } // HystrixCommand 和 HystrixCollapser 的環繞通知 @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()") public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable { Method method = getMethodFromTarget(joinPoint); Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint); if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) { throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " + "annotations at the same time"); } // 建立metaHolder,用於保存元數據 MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method)); MetaHolder metaHolder = metaHolderFactory.create(joinPoint); // 建立HystrixCommand,HystrixInvokable是父接口 HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder); ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ? metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType(); // 執行HystrixCommand Object result; try { if (!metaHolder.isObservable()) { result = CommandExecutor.execute(invokable, executionType, metaHolder); } else { result = executeObservable(invokable, executionType, metaHolder); } } catch (HystrixBadRequestException e) { throw e.getCause() != null ? e.getCause() : e; } catch (HystrixRuntimeException e) { throw hystrixRuntimeExceptionToThrowable(metaHolder, e); } return result; }
重點是methodsAnnotatedWithHystrixCommand()
環繞通知的實現方法
其中MetaHolder metaHolder = metaHolderFactory.create(joinPoint)
根據joinPoint的信息建立元數據時確定會有初始化默認groupKey、默認commandKey以及默認threadPoolKey的邏輯
private static class CommandMetaHolderFactory extends MetaHolderFactory { @Override public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) { HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class); ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType()); // 建立MetaHolderBuilder MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint); if (isCompileWeaving()) { builder.ajcMethod(getAjcMethodFromTarget(joinPoint)); } return builder.defaultCommandKey(method.getName()) //默認commandKey是方法名 .hystrixCommand(hystrixCommand) .observableExecutionMode(hystrixCommand.observableExecutionMode()) .executionType(executionType) .observable(ExecutionType.OBSERVABLE == executionType) .build(); } } //----------再來看看建立MetaHolderBuilder - metaHolderBuilder() //== MetaHolderFactory#metaHolderBuilder() MetaHolder.Builder metaHolderBuilder(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) { MetaHolder.Builder builder = MetaHolder.builder() .args(args).method(method).obj(obj).proxyObj(proxy) .joinPoint(joinPoint); // 設置fallback方法 setFallbackMethod(builder, obj.getClass(), method); // 設置默認配置 builder = setDefaultProperties(builder, obj.getClass(), joinPoint); return builder; } //== 設置默認配置 setDefaultProperties() private static MetaHolder.Builder setDefaultProperties(MetaHolder.Builder builder, Class<?> declaringClass, final ProceedingJoinPoint joinPoint) { //根據@DefaultProperties註解獲取配置 Optional<DefaultProperties> defaultPropertiesOpt = AopUtils.getAnnotation(joinPoint, DefaultProperties.class); //設置 默認groupKey爲類名simpleName builder.defaultGroupKey(declaringClass.getSimpleName()); //若是存在@DefaultProperties,使用其指定的groupKey、threadPoolKey if (defaultPropertiesOpt.isPresent()) { DefaultProperties defaultProperties = defaultPropertiesOpt.get(); builder.defaultProperties(defaultProperties); if (StringUtils.isNotBlank(defaultProperties.groupKey())) { builder.defaultGroupKey(defaultProperties.groupKey()); } if (StringUtils.isNotBlank(defaultProperties.threadPoolKey())) { builder.defaultThreadPoolKey(defaultProperties.threadPoolKey()); } } return builder; }
因而可知,在構造metaHolder元數據時,經過@HystrixCommand標準方法所在的類名做爲goutpKey,經過方法名做爲commandKey,但沒有指定threadPoolKey
但執行HystrixCommand時是有默認threadPoolKey的,那麼這個默認值從何而來,command又是怎麼初始化線程池的呢??
//----------HystrixCommandFactory#create() public HystrixInvokable create(MetaHolder metaHolder) { HystrixInvokable executable; if (metaHolder.isCollapserAnnotationPresent()) { executable = new CommandCollapser(metaHolder); } else if (metaHolder.isObservable()) { executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); } else { //經過metaHolder構造HystrixCommandBuilder,再建立GenericCommand executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); } return executable; } //----------HystrixCommandBuilderFactory#create() 建立HystrixCommandBuilder //在建立HystrixCommandBuilder時,createGenericSetterBuilder(metaHolder)構造了Setter,是用於設置groupKey、commandKey、threadPoolKey的 public <ResponseType> HystrixCommandBuilder create(MetaHolder metaHolder, Collection<HystrixCollapser.CollapsedRequest<ResponseType, Object>> collapsedRequests) { validateMetaHolder(metaHolder); return HystrixCommandBuilder.builder() .setterBuilder(createGenericSetterBuilder(metaHolder)) //重點:設置setterBuilder .commandActions(createCommandActions(metaHolder)) .collapsedRequests(collapsedRequests) .cacheResultInvocationContext(createCacheResultInvocationContext(metaHolder)) .cacheRemoveInvocationContext(createCacheRemoveInvocationContext(metaHolder)) .ignoreExceptions(metaHolder.getCommandIgnoreExceptions()) .executionType(metaHolder.getExecutionType()) .build(); } //----------createGenericSetterBuilder() 建立SetterBuilder private GenericSetterBuilder createGenericSetterBuilder(MetaHolder metaHolder) { GenericSetterBuilder.Builder setterBuilder = GenericSetterBuilder.builder() .groupKey(metaHolder.getCommandGroupKey()) .threadPoolKey(metaHolder.getThreadPoolKey()) //查看從metaHolder如何獲取threadPoolKey .commandKey(metaHolder.getCommandKey()) .collapserKey(metaHolder.getCollapserKey()) .commandProperties(metaHolder.getCommandProperties()) .threadPoolProperties(metaHolder.getThreadPoolProperties()) .collapserProperties(metaHolder.getCollapserProperties()); if (metaHolder.isCollapserAnnotationPresent()) { setterBuilder.scope(metaHolder.getHystrixCollapser().scope()); } return setterBuilder.build(); } //若是使用了Command註解,從註解指定的threadPoolKey 和 defaultThreadPoolKey二選一,之前者爲主 //本例中,既沒有經過註解指定threadPoolKey,也沒有defaultThreadPoolKey public String getThreadPoolKey() { return isCommandAnnotationPresent() ? get(hystrixCommand.threadPoolKey(), defaultThreadPoolKey) : ""; }
從上面看,HystrixCommandBuilder都構造完成了,尚未設置threadPoolKey
下面是經過HystrixCommandBuilder
做爲參數建立GenericCommand()
,即經過HystrixCommandBuilder
建立HystrixCommand
GenericCommand的類圖爲:
可見GenericCommand
集成關係,從AbstractHystrixCommand
--> HystrixCommand
--> AbstractCommand
,最終他們都是HystrixInvokeable
接口的實現了,便可被Hystrix調用的
向上進入父類構造,HystrixCommand(Setter setter)
protected HystrixCommand(Setter setter) { // use 'null' to specify use the default this(setter.groupKey, setter.commandKey, setter.threadPoolKey, null, null, setter.commandPropertiesDefaults, setter.threadPoolPropertiesDefaults, null, null, null, null, null); }
從setter中獲取了groupKey、commandKey、threadPoolKey、commandPropertiesDefaults、threadPoolPropertiesDefaults,其它參數爲null
進入到 AbstractCommand構造方法,封裝了構造一個HystrixCommand的基本上全部元素的邏輯
protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { this.commandGroup = initGroupKey(group); //初始化commandGroupKey this.commandKey = initCommandKey(key, getClass()); //初始化commandKey this.properties = initCommandProperties(this.commandKey, propertiesStrategy, commandPropertiesDefaults); //初始化commandProperties this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get()); //初始化threadPoolKey this.metrics = initMetrics(metrics, this.commandGroup, this.threadPoolKey, this.commandKey, this.properties); //初始化metrics this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics); //初始化斷路器 this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults); //初始化線程池 //Strategies from plugins this.eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(this.commandKey, this.commandGroup, this.metrics, this.circuitBreaker, this.properties); this.executionHook = initExecutionHook(executionHook); this.requestCache = HystrixRequestCache.getInstance(this.commandKey, this.concurrencyStrategy); this.currentRequestLog = initRequestLog(this.properties.requestLogEnabled().get(), this.concurrencyStrategy); /* fallback semaphore override if applicable */ this.fallbackSemaphoreOverride = fallbackSemaphore; /* execution semaphore override if applicable */ this.executionSemaphoreOverride = executionSemaphore; }
接下來主要看是如何初始化threadPoolKey,以及threadPool的
initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get())
參數:
threadPoolKey -- 指定的 或 默認的threadPoolKey
this.commandGroup -- 當前的groupKey
this.properties.executionIsolationThreadPoolKeyOverride().get()) -- 字符串類型,容許動態覆蓋修改HystrixThreadPoolKey的值,並將動態更新HystrixCommand執行的HystrixThreadPool,這個override值的典型值是null,而且在構造HystrixCommandProperties時override全局的配置爲null
// threadpool doesn't have a global override, only instance level makes sense this.executionIsolationThreadPoolKeyOverride = forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", null).build();
接着看 initThreadPoolKey() 方法內部
/* * ThreadPoolKey * * This defines which thread-pool this command should run on. * * It uses the HystrixThreadPoolKey if provided, then defaults to use HystrixCommandGroup. * 若是提供了threadPoolKey,就使用,不然默認使用groupKey * * It can then be overridden by a property if defined so it can be changed at runtime. * 能夠被threadPoolKeyOverride在運行時動態覆蓋 */ private static HystrixThreadPoolKey initThreadPoolKey(HystrixThreadPoolKey threadPoolKey, HystrixCommandGroupKey groupKey, String threadPoolKeyOverride) { if (threadPoolKeyOverride == null) { // we don't have a property overriding the value so use either HystrixThreadPoolKey or HystrixCommandGroup if (threadPoolKey == null) { /* * use HystrixCommandGroup if HystrixThreadPoolKey is null * 若是HystrixThreadPoolKey爲空,使用groupKey做爲threadPoolKey */ return HystrixThreadPoolKey.Factory.asKey(groupKey.name()); } else { return threadPoolKey; } } else { // we have a property defining the thread-pool so use it instead return HystrixThreadPoolKey.Factory.asKey(threadPoolKeyOverride); } }
可見,在最開始構造HystrixCommand時,threadPoolKeyOverride爲null,且沒有本身指定的threadPoolKey,也沒有默認的threadPoolKey,那麼將使用groupKey做爲threadPoolKey
因此,默認使用groupKey做爲threadPoolKey,而group默認值是標註了@HystrixCommand的類名
最後,看一下如何根據threadPoolKey,初始化threadPool
//----------AbstractCommand#initThreadPool() private static HystrixThreadPool initThreadPool(HystrixThreadPool fromConstructor, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) { // fromConstructor爲null,使用HystrixThreadPool.Factory建立線程池 if (fromConstructor == null) { // get the default implementation of HystrixThreadPool return HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults); } else { return fromConstructor; } } //----------HystrixThreadPool.Factory#getInstance() 獲取HystrixThreadPool實例 static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) { // get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work String key = threadPoolKey.name(); // this should find it for all but the first time // 從緩存threadPools中獲取HystrixThreadPool,有則直接返回 HystrixThreadPool previouslyCached = threadPools.get(key); if (previouslyCached != null) { return previouslyCached; } // if we get here this is the first time so we need to initialize // 第一次初始化HystrixThreadPool synchronized (HystrixThreadPool.class) { if (!threadPools.containsKey(key)) { threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder)); } } return threadPools.get(key); }
先根據threadPoolKey嘗試從threadPools這個ConcurrentHashMap<String, HystrixThreadPool>
中獲取,即從線程池緩存中獲取,有就直接返回previouslyCached以前的緩存,若是沒有,synchromized對HystrixThreadPool類上鎖後,再次判斷仍是沒有threadPoolKey的緩存,就 new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder)
//----------new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder) public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) { this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults); //threadPoolProperties HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); //併發策略 this.queueSize = properties.maxQueueSize().get(); //線程池隊列大小 //建立HystrixThreadPoolMetrics,其中concurrencyStrategy.getThreadPool()會建立線程池 this.metrics = HystrixThreadPoolMetrics.getInstance(threadPoolKey, concurrencyStrategy.getThreadPool(threadPoolKey, properties), properties); this.threadPool = this.metrics.getThreadPool(); this.queue = this.threadPool.getQueue(); /* strategy: HystrixMetricsPublisherThreadPool */ HystrixMetricsPublisherFactory.createOrRetrievePublisherForThreadPool(threadPoolKey, this.metrics, this.properties); } //----------HystrixConcurrencyStrategy#getThreadPool(HystrixThreadPoolKey, HystrixThreadPoolProperties) // concurrencyStrategy.getThreadPool()時會建立ThreadPoolExecutor public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { final ThreadFactory threadFactory = getThreadFactory(threadPoolKey); final boolean allowMaximumSizeToDivergeFromCoreSize = threadPoolProperties.getAllowMaximumSizeToDivergeFromCoreSize().get(); //是否容許maximumSize生效 final int dynamicCoreSize = threadPoolProperties.coreSize().get(); //動態coreSize final int keepAliveTime = threadPoolProperties.keepAliveTimeMinutes().get(); //大於coreSize的線程,未使用的保活時間 final int maxQueueSize = threadPoolProperties.maxQueueSize().get(); //線程隊列最大值 final BlockingQueue<Runnable> workQueue = getBlockingQueue(maxQueueSize); //容許使用maximumSize if (allowMaximumSizeToDivergeFromCoreSize) { final int dynamicMaximumSize = threadPoolProperties.maximumSize().get(); //dynamicCoreSize > dynamicMaximumSize,打印error if (dynamicCoreSize > dynamicMaximumSize) { logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " + dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " + dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value"); return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory); } //dynamicCoreSize <= dynamicMaximumSize,正常 else { return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory); } } else { //不容許使用maximumSize return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory); } }
至此,線程池建立完畢
threadPoolKey的默認值是groupKey,而groupKey默認值是@HystrixCommand標註的方法所在類名
能夠經過在類上加@DefaultProperties( threadPoolKey="xxx" )設置默認的threadPoolKey
能夠經過@HystrixCommand( threadPoolKey="xxx" ) 指定當前HystrixCommand實例的threadPoolKey
threadPoolKey用於從線程池緩存中獲取線程池 和 初始化建立線程池,因爲默認以groupKey即類名爲threadPoolKey,那麼默認全部在一個類中的HystrixCommand共用一個線程池
動態配置線程池 -- 能夠經過hystrix.command.HystrixCommandKey.threadPoolKeyOverride=線程池key
動態設置threadPoolKey,對應的HystrixCommand所使用的線程池也會從新建立,還能夠繼續經過hystrix.threadpool.HystrixThreadPoolKey.coreSize=n
和hystrix.threadpool.HystrixThreadPoolKey.maximumSize=n
動態設置線程池大小
注意: 經過threadPoolKeyOverride動態修改threadPoolKey以後,hystrixCommand會使用新的threadPool,可是老的線程池還會一直存在,並無觸發shutdown的機制