轉自:http://www.ibm.com/developerworks/cn/java/l-safethread/
對一些代碼按自己的理解修改
在JAVA環境中,一個任務一般是由一個獨立線程來引導實現的,如果在執行過程中,某一個線程發生異常(產生的原因很多,比如軟件升級、運行環境改變、系統資搶佔等),那麼該線程就會停止運行,直到下次任務重新被提交。對於實時環境來說當前任務是失敗的。我們無法預測和完全避免異常的發生,但是可以通過一些技術手段來跟蹤任務的狀態,從而及時發現問題並恢復正常,減少損失。
一個簡單的例子:
A任務每秒執行一個簡單的代數運算 j = 1/ i,並打印結果。我們故意在其中設置了一個異常陷阱,使得執行過程中出現一次被0除的算術異常,下面結合這個例子講述監控原理。
public class ATask extends Thread {
/**
* A任務定期修改自己的sign標誌,sign是一個布爾變量,理論上只要A沒有死,
* 那麼sign肯定是週期變化的(和心跳概念差不多)
*/
public boolean sign = false;
Stakeout m;
ATask(Stakeout m) {
m = m;
start();
}
public void run() {
try {
for (int i = -3; i <= 5; i++) {
int j = 1 / i; // 人爲設置過程中陷阱
sign = !sign; // 活動狀態
System.out.println("i=" + i + ": status=" + sign);
try {
sleep(2000);
} catch (InterruptedException ie) {
System.out.println("A is Interrupted!");
}
}
m.Keepchecking = false; //A 正常結束後關閉監控線程
System.out.println("A is Ending M");
} catch (Exception e) {
System.out.println("A become Exception!");
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("線程:"+Thread.currentThread()+" is over by gc~~~~~~~~~~~~~");
}
}
爲了適應監控,在A任務中相應增加一些可以被監控的狀態和行爲:
sign狀態標誌
sign= !sign; 改變狀態
類似於心跳,如果監控方遇到sign都是同一個說明,A出現異常了。
爲了監控A,我們設計了一個監控線程Stakeout。Stakeout中定義了一些關鍵邏輯變量:
Keepchecking 持續監控標誌
laststatus 保存上次監控狀態
maydeadtimes 監控線程可能死亡的計數器
maydeadtimeout 定義判斷線程死亡的邊界條件
deadtimes 監控線程死亡次數的計數器
deadtimeout 定義判斷線程不正常的邊界條件
/**
* 監控線程
*/
public class Stakeout extends Thread {
public static boolean Keepchecking = true; // 持續監控標誌
boolean laststatus; //保存上次監控狀態
int maydeadtimes = 0; //監控線程可能死亡的計數器
int maydeadtimeout = 3;//定義判斷線程死亡的邊界條件
int deadtimes = 0; //監控線程死亡次數的計數器
int deadtimeout = 3; //定義判斷線程不正常的邊界條件
ATask a;
Stakeout() {
start();
}
public void run() {
schedule();
while (Keepchecking) {
laststatus = a.sign;
try {
sleep(2000);
} catch (InterruptedException e) {
System.out.println("M is Interrupted!");
}
System.out.println("M read A status = " + a.sign);
//如果過了一段時間記錄的狀態與a中的狀態相同,說明可能存在線程死亡的情況
if (laststatus == a.sign) {
// 線程可能死亡的次數超過設定值
if (++maydeadtimes >= maydeadtimeout) {
//實際線程死亡的次數
if (++deadtimes >= deadtimeout) {
System.out.println("Alert! A is unstable, M will stop it");
a = null;
break;
} else {
System.out.println("A is deaded!");
schedule();
System.out.println("M is restarting A!\n____________________________\n");
}
}
} else {
maydeadtimes = 0;
}
}
}
/**
* 初始化任務
*/
private void schedule() {
a = new ATask(this);
}
}
整個監控就是圍繞這些狀態標誌和行爲展開的。A任務定期修改自己的sign標誌,sign是一個布爾變量,理論上只要A沒有死,那麼sign肯定是週期變化的(和心跳概念差不多),Stakeout需要做的就是監控sign的變化。爲了避免一些偶然因素導致的誤判,我們在Stakeout中設置了一些計數器和邊界值(maydeadtimes和maydeadtimeout),當Stakeout發現A的的sign沒有變化那就認爲A是出現假死狀態,如果連續3次出現假死狀態那麼判斷A掛了,嘗試重啓A線程,如果總計出現了3次A掛了的情況,那麼不在重啓了,可以基本肯定不是由於偶然因素引起的,需要對A的代碼或系統的環境進行檢查。Stakeout會發出告警,通知必須要對A進行審查了,然後清空A,自己自動安全退出。如果在覈心調度程序中設置一個標誌接受Stakeout們的告警,就可以有足夠理由終止其他任務的執行。
可以看見,在A任務發生異常期間,Stakeout承擔了核心調度程序的維護功能。特別是當任務數量比較多的情況,核心調度程序只能採用排隊方式處理任務異常,而且由於處理異常的複雜程度不同,無法保證對多任務異常的實時處理。
還要考慮正常情況下A和Stakeout的關係。核心調度程序通過Stakeout啓動A任務後,Stakeout處於持續監控狀態,當A正常結束任務後,A需要通知Stakeout結束監控,這樣,當A進入休眠狀態後,Stakeout也不會佔用內存空間,提高了系統資源的利用率。