ScheduledThreadPoolExecutor巨坑解決
概述
最近在做一些優(yōu)化的時(shí)候用到了ScheduledThreadPoolExecutor。
雖然知道這個(gè)玩意,但是也很久沒(méi)用,本著再了解了解的心態(tài),到網(wǎng)上搜索了一下,結(jié)果就發(fā)現(xiàn)網(wǎng)上有些博客在說(shuō)ScheduledThreadPoolExecutor有巨坑?。?!
瞬間,我的興趣就被激起來(lái)了,馬上進(jìn)去學(xué)習(xí)了一波~
不看不知道,看完后馬上把我的代碼坑給填上了~
下面就當(dāng)記錄一下吧,順便也帶大家了解了解,大家看完后也趕緊看看自己公司的項(xiàng)目代碼有沒(méi)有這種漏洞,有的話趕緊給填上,升級(jí)加薪指日可待?。。?/p>
坑是啥?
先看下面案例代碼
public class ScheduledThreadPoolExecutorTest {
public static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
public static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
// 模擬業(yè)務(wù)邏輯
int num = atomicInteger.getAndIncrement();
// 模擬出現(xiàn)異常
if (num > 3) {
throw new RuntimeException("定時(shí)任務(wù)執(zhí)行異常");
}
System.out.println("別坑我!");
}, 0, 1, TimeUnit.SECONDS);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduledThreadPoolExecutor.shutdown();
}
}
案例代碼邏輯很簡(jiǎn)單,主線程等待5秒后關(guān)閉線程池,定時(shí)任務(wù)執(zhí)行三次后模擬拋出RuntimeException
但是我們看看執(zhí)行結(jié)果,只執(zhí)行了三次!
因?yàn)槟撤N情況下,定時(shí)任務(wù)在執(zhí)行第四次時(shí)出現(xiàn)異常,從而導(dǎo)致任務(wù)調(diào)度被取消,不會(huì)繼續(xù)執(zhí)行
而且,異常信息也沒(méi)有對(duì)外拋出!

那么咋解決嘞?try-catch就行了唄~

可以看到執(zhí)行結(jié)果,雖然執(zhí)行異常,但是任務(wù)卻還是一直在調(diào)度~
代碼里使用工具類對(duì)Runnable任務(wù)包了一層,就是加了try-catch
public class RunnableDecoratorUtil {
public static Runnable runnableDecorator(Runnable runnable) {
return () -> {
try {
runnable.run();
} catch (Exception e) {
e.printStackTrace();
}
};
}
}
okok,總結(jié)一下,坑就是: 任務(wù)如果拋出異常就不會(huì)繼續(xù)調(diào)度執(zhí)行了,趕緊去try-catch吧?。。?/p>
大家趕緊去看看自己代碼有沒(méi)有這個(gè)坑吧,本文到此結(jié)束!
開(kāi)個(gè)玩笑~ 光知道有坑哪能不知道為啥坑,接下來(lái)就帶大家了解一下坑到底是啥!
怎么坑的?
直接進(jìn)入scheduleAtFixedRate源碼查看
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
// 參數(shù)校驗(yàn)
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0L)
throw new IllegalArgumentException();
// 將任務(wù)、執(zhí)行時(shí)間、周期等封裝到ScheduledFutureTask內(nèi)
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period),
sequencer.getAndIncrement());
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
// 延時(shí)執(zhí)行
delayedExecute(t);
return t;
}
因?yàn)槲覀兲峤坏娜蝿?wù)被封裝在ScheduledFutureTask,所以我們直接來(lái)看ScheduledFutureTask的run方法
public void run() {
// 校驗(yàn)當(dāng)前狀態(tài)是否還能執(zhí)行任務(wù),不能執(zhí)行直接cancel取消
if (!canRunInCurrentRunState(this))
cancel(false);
else if (!isPeriodic())
// 如果不是周期性的,直接調(diào)用父類run方法執(zhí)行一次即可
super.run();
else if (super.runAndReset()) { // 周期性任務(wù),調(diào)用runAndReset運(yùn)行并重置
// 設(shè)置下一次的執(zhí)行時(shí)間
setNextRunTime();
// 將任務(wù)重新加入隊(duì)列,進(jìn)行調(diào)度
reExecutePeriodic(outerTask);
}
}
public boolean isPeriodic() {
return period != 0;
}
我們是周期性任務(wù),所以直接看runAndReset源碼
protected boolean runAndReset() {
// 檢查任務(wù)狀態(tài),cas機(jī)制防止并發(fā)執(zhí)行任務(wù)
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return false;
// 默認(rèn)不周期執(zhí)行任務(wù)
boolean ran = false;
// state為NEW狀態(tài)
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
// 執(zhí)行任務(wù)
c.call();
// 正常執(zhí)行成功,設(shè)置為true代表周期執(zhí)行
ran = true;
} catch (Throwable ex) {
// 但是,如果執(zhí)行異常!則不會(huì)將ran = true,所以最終返回false
setException(ex);
}
}
} finally {
runner = null;
// 設(shè)置為NEW狀態(tài)
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
// 正常執(zhí)行完之后,結(jié)果為true,能夠周期執(zhí)行
// 但如果執(zhí)行異常,ran為false,返回結(jié)果為false
return ran && s == NEW;
}
通過(guò)上面源碼,我們可以很清楚的了解到,就是因?yàn)槿蝿?wù)執(zhí)行異常,且沒(méi)有被try-catch,所以導(dǎo)致任務(wù)沒(méi)有被再次加入到隊(duì)列中進(jìn)行調(diào)度。
并且通過(guò)文章開(kāi)頭,我們還能看到任務(wù)執(zhí)行異常,但是卻沒(méi)有拋出異常信息
那是因?yàn)楫惓1环庋b了,只有調(diào)用get方法時(shí),才會(huì)拋出異常

/** The result to return or exception to throw from get() */
private Object outcome;
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
protected void setException(Throwable t) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
// 將異常信息賦值給outcome
// outcome既可以為任務(wù)執(zhí)行結(jié)果也可以為異常信息
outcome = t;
// 將state設(shè)置為異常狀態(tài),state=3
STATE.setRelease(this, EXCEPTIONAL); // final state
finishCompletion();
}
}
// 調(diào)用get方法阻塞獲取結(jié)果
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
// 此時(shí)s = EXCEPTIONAL = 3
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
// 所以會(huì)走到這里,對(duì)外拋出了任務(wù)執(zhí)行的異常
throw new ExecutionException((Throwable)x);
}
總結(jié)
通過(guò)上面對(duì)源碼的了解,我們了解到,如果周期性任務(wù)執(zhí)行出現(xiàn)異常,并且沒(méi)有被try-catch,會(huì)導(dǎo)致該周期性任務(wù)不會(huì)再被放入到隊(duì)列中進(jìn)行調(diào)度執(zhí)行。
以上就是ScheduledThreadPoolExecutor巨坑解決的詳細(xì)內(nèi)容,更多關(guān)于ScheduledThreadPoolExecutor坑的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java實(shí)現(xiàn)簡(jiǎn)易撲克牌游戲
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)易撲克牌游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
SpringBoot動(dòng)態(tài)更新yml文件
在系統(tǒng)運(yùn)行過(guò)程中,可能由于一些配置項(xiàng)的簡(jiǎn)單變動(dòng)需要重新打包啟停項(xiàng)目,這對(duì)于在運(yùn)行中的項(xiàng)目會(huì)造成數(shù)據(jù)丟失,客戶操作無(wú)響應(yīng)等情況發(fā)生,針對(duì)這類情況對(duì)開(kāi)發(fā)框架進(jìn)行升級(jí)提供yml文件實(shí)時(shí)修改更新功能,這篇文章主要介紹了SpringBoot動(dòng)態(tài)更新yml文件2023-01-01
因不會(huì)遠(yuǎn)程debug調(diào)試我被項(xiàng)目經(jīng)理嘲笑了
這篇文章主要介紹了遠(yuǎn)程debug調(diào)試的相關(guān)內(nèi)容,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
Maven配置項(xiàng)目依賴使用本地倉(cāng)庫(kù)的方法匯總(小結(jié))
這篇文章主要介紹了Maven配置項(xiàng)目依賴使用本地倉(cāng)庫(kù)的方法匯總(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
java shiro實(shí)現(xiàn)退出登陸清空緩存
本篇文章主要介紹了java shiro實(shí)現(xiàn)退出登陸清空緩存,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
Maven的porfile與SpringBoot的profile結(jié)合使用案例詳解
這篇文章主要介紹了Maven的porfile與SpringBoot的profile結(jié)合使用,通過(guò)maven的profile功能,在打包的時(shí)候,通過(guò)-P指定maven激活某個(gè)pofile,這個(gè)profile里面配置了一個(gè)參數(shù)activatedProperties,不同的profile里面的這個(gè)參數(shù)的值不同,需要的朋友可以參考下吧2021-12-12
Java類加載異常:java.lang.ClassNotFoundException解決方法
這篇文章主要給大家介紹了關(guān)于Java類加載異常:java.lang.ClassNotFoundException的解決方法,異常是Java編程語(yǔ)言中的一個(gè)標(biāo)準(zhǔn)異常類,它繼承自類,當(dāng)在運(yùn)行時(shí)嘗試加載類時(shí),如果系統(tǒng)找不到指定的類文件就會(huì)拋出該異常,需要的朋友可以參考下2023-11-11
Java數(shù)據(jù)結(jié)構(gòu)之稀疏矩陣定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之稀疏矩陣定義與用法,結(jié)合實(shí)例形式分析了java稀疏矩陣的定義、運(yùn)算、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
Java數(shù)據(jù)庫(kù)連接池之proxool_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Proxool是一種Java數(shù)據(jù)庫(kù)連接池技術(shù)。方便易用,便于發(fā)現(xiàn)連接泄漏的情況2017-08-08

