前言:
我們?nèi)粘?倳?huì)碰到這樣的需求:
- 把這個(gè)任務(wù)放到另外一個(gè)線程執(zhí)行。
- 我需要周期性執(zhí)行任務(wù)的線程。那我們通常都是怎么解決這個(gè)問題呢?
- 對(duì)于問題1,最簡(jiǎn)單的做法就是new thread,然后結(jié)束了。但在生產(chǎn)環(huán)境中,你常常會(huì)困擾,我這個(gè)線程到底執(zhí)行沒,或者是否正在執(zhí)行。任務(wù)報(bào)錯(cuò)了日志在哪里呢?
- 對(duì)于問題2,我們可能會(huì)選擇定時(shí)器,ScheduledExecutor,但如果你想追蹤你的線程運(yùn)行情況,或者捕獲異常,這都沒法隨心所欲
針對(duì)上面常見的兩個(gè)問題,我自己封裝了一個(gè)標(biāo)準(zhǔn)的后臺(tái)線程模型
一、完整代碼實(shí)現(xiàn)
/**
* DefaultThread 后臺(tái)標(biāo)準(zhǔn)線程
*/
public final class DefaultThread extends Thread {
/** log */
private static final Logger log = InternalLoggerFactory.getLogger(DefaultThread.class);
/** 標(biāo)準(zhǔn)后臺(tái)線程循環(huán)間隔時(shí)間 - 1分鐘 */
public static final long DEFAULT_INTERVAL = 60 * 1000;
/** 標(biāo)準(zhǔn)后臺(tái)線程循環(huán)最小間隔 - 300ms */
public static final long MIN_INTERVAL = 300;
/** 默認(rèn)日志輸出級(jí)別 -- debug */
public static final LogLevel DEFAULT_LEVEL = LogLevel.DEBUG;
private String name;
private Runnable runnable;
private boolean runOnce;
private volatile boolean stop;
private volatile long interval;
private volatile LogLevel level = DEFAULT_LEVEL;
/**
* 構(gòu)造函數(shù)
* <p>將構(gòu)造一個(gè)標(biāo)準(zhǔn)后臺(tái)線程,每分鐘運(yùn)行1次
* @param name
* @param runnable
*/
public DefaultThread(String name, Runnable runnable) {
super(name);
this.name = name;
this.runnable = runnable;
this.runOnce = false;
this.stop = false;
this.interval = DEFAULT_INTERVAL;
}
/**
* 構(gòu)造函數(shù)
* <p>將構(gòu)造一個(gè)標(biāo)準(zhǔn)后臺(tái)線程
* @param name
* @param runnable
* @param runOnce
* @param interval
*/
public DefaultThread(String name, Runnable runnable, boolean runOnce) {
super(name);
this.name = name;
this.runnable = runnable;
this.runOnce = runOnce;
this.stop = false;
this.interval = DEFAULT_INTERVAL;
}
/**
* 構(gòu)造函數(shù)
* <p>將構(gòu)造一個(gè)標(biāo)準(zhǔn)后臺(tái)線程,指定間隔運(yùn)行一次(不能小于最小間隔時(shí)間{@link #MIN_INTERVAL}})
* @param name
* @param runnable
* @param runOnce
* @param interval
*/
public DefaultThread(String name, Runnable runnable, long interval) {
super(name);
this.name = name;
this.runnable = runnable;
this.runOnce = false;
this.stop = false;
this.interval = Math.max(MIN_INTERVAL, interval);
}
@Override
public void start() {
super.start();
}
public void stop() {
try {
this.interrupt();
} catch (Exception e) {
// Ignore
}
this.stop = true;
}
/**
* 獲得線程名稱
*/
public String getThreadName() {
return this.name;
}
/**
* 設(shè)置日志隔離級(jí)別
*/
public void setLogLevel(LogLevel level) {
this.level = level;
}
/**
* 執(zhí)行任務(wù)
* @see JAVA.lang.Thread#run()
*/
public final void run() {
if (runOnce) {
runOnce();
return;
}
while (!stop) {
runOnce();
try {
sleep(interval);
} catch (InterruptedException e) {
// Ignore this Exception
}
}
}
/**
* 執(zhí)行一次
*/
private void runOnce() {
long startTime = 0;
if (log.isLogEnabled(level)) {
startTime = System.currentTimeMillis();
}
try {
runnable.run();
} catch (Throwable t) {
log.error("thread run error [name:{}, runOnce:{}]", t, name, runOnce);
}
if (log.isLogEnabled(level)) {
log.log(level, "{}#{}", (System.currentTimeMillis() - startTime));
}
}
二、代碼解讀
線程名稱變成強(qiáng)制性
創(chuàng)建標(biāo)準(zhǔn)線程,必須指定線程名稱,這是為了方便在jstack等工具中追蹤
可以定義為周期性執(zhí)行
周期性執(zhí)行中做了預(yù)防措施,防止定義過小的周期,引起死循環(huán),占用過高CPU
每次執(zhí)行可追蹤
每次執(zhí)行,如果在debug模式下將記錄執(zhí)行時(shí)間,對(duì)執(zhí)行異常也進(jìn)行捕獲打印日志,方便追蹤bug
線程可停止
Java提供的線程并沒有提供停止方法,該封裝中通過一個(gè)標(biāo)志位實(shí)現(xiàn)該功能,能夠中斷線程
結(jié)語:
線程雖然簡(jiǎn)單,但在實(shí)際使用中也要注意規(guī)范,每個(gè)線程都是隨意new,隨意使用的話會(huì)造成后續(xù)維護(hù),bug追蹤方便極大的困難。建議項(xiàng)目中的線程都遵從同一個(gè)標(biāo)準(zhǔn)。本文僅是個(gè)人實(shí)踐拙見,歡迎拍磚!