本文分析了Eclipse中多線(xiàn)程程序的實(shí)現(xiàn),討論了在Eclipse客戶(hù)端程序開(kāi)發(fā)中應(yīng)用多線(xiàn)程的方法和要注意的問(wèn)題,同時(shí)也討論了多線(xiàn)程程序的一些調(diào)試和問(wèn)題解決的方法。
Eclipse 作為一個(gè)開(kāi)發(fā)平臺(tái),使用越來(lái)越廣泛,基于Eclipse Rich Client Platform開(kāi)發(fā)的客戶(hù)端程序也越來(lái)越多。在當(dāng)今越來(lái)越復(fù)雜的應(yīng)用環(huán)境中,我們的客戶(hù)端程序不可避免的要同時(shí)進(jìn)行多任務(wù)的處理。一個(gè)優(yōu)異的客戶(hù)端程序都會(huì)允許用戶(hù)同時(shí)啟動(dòng)多個(gè)任務(wù),從而大大提高用戶(hù)的工作效率以及用戶(hù)體驗(yàn)。本文中我們來(lái)談?wù)凟clipse中實(shí)現(xiàn)多任務(wù)的方式。
在我們基于Eclipse的Java程序中,我們有很多種方式提供多任務(wù)的實(shí)現(xiàn)。熟悉Java的朋友立即會(huì)想到Java的Thread類(lèi),這是Java中使用最多的一個(gè)實(shí)現(xiàn)多任務(wù)的類(lèi)。Eclipse平臺(tái)為多任務(wù)處理提供了自己的API,那就是Job以及UIJob。Eclipse中的Job是對(duì)Java Thread的一個(gè)封裝,為我們實(shí)現(xiàn)多任務(wù)提供了更方便的接口。以下是Job的基本用法:
清單 1. Job用法示例
Job job = new Job(“Job Name”){
protected IStatus run(IProgressMonitor monitor) {
// 在這里添加你的任務(wù)代碼
return Status.OK_STATUS;
}
};
job.schedule(1133);//delaytime
job.setUser(true);//if true show UI
job.setPriority(priority)
在Eclipse 中我們也會(huì)經(jīng)常用到Display.asynchExec() 和Display.synchExec()來(lái)啟動(dòng)任務(wù)的執(zhí)行。這兩個(gè)方法主要為了方便我們完成界面操作的任務(wù)。以下是 Display.asynchExec()的用法,Display.synchExec()和它類(lèi)似。
清單 2. Display.synchExec()用法示例
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// 在這里添加你的任務(wù)代碼
}
});
通常,在Eclipse中我們最好使用Eclipse提供的Job接口來(lái)實(shí)現(xiàn)多任務(wù),而不是使用Java的thread。為什么呢?主要有以下幾個(gè)原因:
Job是可重用的工作單元,一個(gè)Job我們可以很方便的讓它多次執(zhí)行。 Job提供了方便的接口,使得我們?cè)谔幚碇心軌蚝芊奖愕呐c外界交流,報(bào)告當(dāng)前的執(zhí)行進(jìn)度 Eclipse提供了相應(yīng)的機(jī)制使得程序員可以方便的介入Job的調(diào)度,例如我們可以方便的實(shí)現(xiàn)每次只有一個(gè)同一類(lèi)型的Job在運(yùn)行 Eclipse缺省提供了Job管理的程序,可以查看當(dāng)前所有的Job和它們的進(jìn)度,也提供UI終止、暫停、繼續(xù)指定的Job 使 用Job可以提高程序的性能,節(jié)省線(xiàn)程創(chuàng)建和銷(xiāo)毀的開(kāi)銷(xiāo)。Eclipse中的Job封裝了線(xiàn)程池的實(shí)現(xiàn)。當(dāng)我們啟動(dòng)一個(gè)Job時(shí),Eclipse不會(huì)馬上 新建一個(gè)Thread,它會(huì)在它的線(xiàn)程池中尋找是否有空閑的線(xiàn)程,如果有空閑線(xiàn)程,就會(huì)直接用空閑線(xiàn)程運(yùn)行你的Job。一個(gè)Job終止時(shí),它所對(duì)應(yīng)的線(xiàn)程 也不會(huì)立即終止,它會(huì)被返回到線(xiàn)程池中以備重復(fù)利用。這樣,我們可以節(jié)省創(chuàng)建和銷(xiāo)毀線(xiàn)程的開(kāi)銷(xiāo) 下面我們從幾個(gè)方面來(lái)討論Eclipse中Job的實(shí)現(xiàn)和使用方面的問(wèn)題。
Eclipse中Job的實(shí)現(xiàn)
Eclipse 的核心包中提供了一個(gè)JobManager類(lèi),它實(shí)現(xiàn)了IJobManager接口,Eclipse中Job的管理和調(diào)度都是由JobManager來(lái)實(shí)現(xiàn)的。 JobManager維護(hù)有一個(gè)線(xiàn)程池,用來(lái)運(yùn)行Job。當(dāng)我們調(diào)用Job的schedule方法后,這個(gè)Job會(huì)被JobManager首先放到一個(gè) Job運(yùn)行的等待隊(duì)列中去。之后,JobManager會(huì)通知線(xiàn)程池有新的Job加入了運(yùn)行等待隊(duì)列。線(xiàn)程池會(huì)找出一個(gè)空閑的線(xiàn)程來(lái)運(yùn)行Job,如果沒(méi)有空閑線(xiàn)程,線(xiàn)程池會(huì)創(chuàng)建一個(gè)新的線(xiàn)程來(lái)運(yùn)行Job。一旦Job運(yùn)行完畢,運(yùn)行Job的線(xiàn)程會(huì)返回到線(xiàn)程池中以備下次使用。從上面Job運(yùn)行的過(guò)程我們可以看到,JobManager介入了一個(gè)Job運(yùn)行的全過(guò)程,它了解Job什么時(shí)候開(kāi)始,什么時(shí)候結(jié)束,每一時(shí)候Job的運(yùn)行狀態(tài)。JobManager將這些Job運(yùn)行的信息以接口的方式提供給用戶(hù),同時(shí)它也提供了接口讓我們可以介入Job的調(diào)度等,從而我們擁有了更加強(qiáng)大的控制Job的能力。
為了我們更方便的了解Job所處的狀態(tài),JobManager設(shè)置Job的一個(gè)狀態(tài)標(biāo)志位,我們可以通過(guò)Job的getState方法獲得Job當(dāng)前的狀態(tài)值以了解其狀態(tài):
NONE:當(dāng)一個(gè)Job剛構(gòu)造的時(shí)候,Job就會(huì)處于這種狀態(tài)。當(dāng)一個(gè)Job執(zhí)行完畢(包括被取消)后,Job的狀態(tài)也會(huì)變回這種狀態(tài)。 WAITING:當(dāng)我們調(diào)用了Job的shedule方法,JobManager會(huì)將Job放入等待運(yùn)行的Job隊(duì)列,這時(shí)Job的狀態(tài)為WAITING. RUNNING:當(dāng)一個(gè)Job開(kāi)始執(zhí)行,Job的狀態(tài)會(huì)變?yōu)镽UNNING。 SLEEPING: 當(dāng)我們調(diào)用Job的sleep方法后,Job會(huì)變成這一狀態(tài)。當(dāng)我們調(diào)用schudule方法的時(shí)候帶上延時(shí)的參數(shù),Job的狀態(tài)也會(huì)轉(zhuǎn)入這一狀態(tài),在這 一段延時(shí)等待的時(shí)間中,Job都處于這一狀態(tài)。這是一種睡眠狀態(tài),Job在這種狀態(tài)中時(shí)不能馬上轉(zhuǎn)入運(yùn)行。我們可以調(diào)用Job的wakeup方法來(lái)將 Job喚醒。這樣,Job又會(huì)轉(zhuǎn)入WAITING狀態(tài)等待運(yùn)行。
Eclipse中的UI線(xiàn)程
另外,在Eclipse的線(xiàn)程處理中,有一個(gè)UI線(xiàn)程的概念。Eclipse程序中的主線(xiàn)程是一個(gè)特殊的線(xiàn)程,程序啟動(dòng)后會(huì)先執(zhí)行這個(gè)線(xiàn)程,也就是我們的 main()函數(shù)所在的線(xiàn)程。作為桌面應(yīng)用程序,我們的主線(xiàn)程主要負(fù)責(zé)界面的響應(yīng)以及繪制界面元素,所以通常我們也叫它UI線(xiàn)程。
以下代碼,編過(guò)SWT應(yīng)用程序的讀者會(huì)非常熟悉。它一般出現(xiàn)在main函數(shù)的結(jié)尾。下面來(lái)仔細(xì)分析一下它的詳細(xì)情況。
//當(dāng)窗口未釋放時(shí)
while (!shell.isDisposed()) {
//如果display對(duì)象事件隊(duì)列中沒(méi)有了等待的事件,就讓該線(xiàn)程進(jìn)入等待狀態(tài)
if (!display.readAndDispatch())
display.sleep();
}
上面的程序?qū)嶋H上就是我們UI線(xiàn)程的處理邏輯:當(dāng)程序啟動(dòng)后,UI線(xiàn)程會(huì)讀取事件等待隊(duì)列,看有沒(méi)有事件等待處理。如果有,它會(huì)進(jìn)行相應(yīng)處理,如果沒(méi)有它會(huì)進(jìn)入睡眠狀態(tài)。如果有新的事件到來(lái),它又會(huì)被喚醒,進(jìn)行處理。UI線(xiàn)程所需要處理的事件包括用戶(hù)的鼠標(biāo)和鍵盤(pán)操作事件,操作系統(tǒng)或程序中發(fā)出的繪制事件。一般來(lái)說(shuō),處理事件的過(guò)程也就是響應(yīng)用戶(hù)操作的過(guò)程。
一個(gè)好的桌面應(yīng)用程序需要對(duì)用戶(hù)的操作作出最快的響應(yīng),也就是說(shuō)我們的UI線(xiàn)程必須盡快的處理各種事件。從我們程序的角度來(lái)說(shuō),在UI線(xiàn)程中我們不能進(jìn)行大量的計(jì)算或者等待,否則用戶(hù)操作事件得不到及時(shí)的處理。通常,如果有大量的計(jì)算或者需要長(zhǎng)時(shí)間等待(例如進(jìn)行網(wǎng)絡(luò)操作或者數(shù)據(jù)庫(kù)操作)時(shí),我們必須將這些長(zhǎng)時(shí)間處理的程序單獨(dú)開(kāi)辟出一個(gè)線(xiàn)程來(lái)執(zhí)行。這樣雖然后臺(tái)運(yùn)行著程序,但也不會(huì)影響界面上的操作。
除主線(xiàn)程之外的所有線(xiàn)程都是非UI線(xiàn)程。在Eclipse程序中,我們所有對(duì)界面元素的操作都必須放到UI線(xiàn)程中來(lái)執(zhí)行,否則會(huì)拋出Exception,所以我們要區(qū)分出UI線(xiàn)程和非UI線(xiàn)程,保證我們對(duì)UI的操作都在UI線(xiàn)程中執(zhí)行。
如何判斷當(dāng)前線(xiàn)程是否UI線(xiàn)程: 你可以通過(guò)調(diào)用Display.getCurrent()來(lái)知道當(dāng)前線(xiàn)程是否是UI線(xiàn)程。如果Display.getCurrent()返回為空,表示當(dāng)前不是UI線(xiàn)程。
Eclipse中使用線(xiàn)程的幾種典型情況
控制Job的并發(fā)運(yùn)行 對(duì)于某些Job,為了避免并發(fā)性問(wèn)題,我們希望同時(shí)只有一個(gè)這樣的Job在運(yùn)行,這時(shí)我們需要控制Job的并發(fā)運(yùn)行。在另一種情況下,我們也需要控制Job 的并發(fā)運(yùn)行:我們?cè)诔绦蛑袑?duì)于一個(gè)任務(wù),我們有可能會(huì)啟動(dòng)一個(gè)Job來(lái)執(zhí)行,對(duì)于少量的任務(wù)來(lái)說(shuō),這是可行的,但是如果我們預(yù)測(cè)可能會(huì)同時(shí)有大量的任務(wù),如果每一個(gè)任務(wù)啟動(dòng)一個(gè)Job,我們同時(shí)啟動(dòng)的Job就會(huì)非常多。這些Job會(huì)侵占大量的資源,影響其他任務(wù)的執(zhí)行。我們可以使用Job的rule來(lái)實(shí)現(xiàn)控制Job的并發(fā)執(zhí)行。簡(jiǎn)單的我們可以通過(guò)下面的代碼實(shí)現(xiàn)。我們先定義一個(gè)如下rule:
private ISchedulingRule Schedule_RULE = new ISchedulingRule() {
public boolean contains(ISchedulingRule rule) {
return this.equals(rule);
}
public boolean isConflicting(ISchedulingRule rule) {
return this.equals(rule);
}
};
對(duì)于需要避免同時(shí)運(yùn)行的Job,我們可以將它們的rule設(shè)成上面定義的rule。如:
myjob1.setRule(Schedule_RULE);
myjob2.setRule(Schedule_RULE);
這樣對(duì)于myjob1和myjob2這兩個(gè)Job,它們不會(huì)再同時(shí)執(zhí)行。Myjob2會(huì)等待myjob1執(zhí)行完再執(zhí)行。這是由Eclipse的JobManager來(lái)提供實(shí)現(xiàn)的。JobManager可以保證所有啟動(dòng)的Job中,任意兩個(gè)Job的rule是沒(méi)有沖突的。我們?cè)谏厦娑x的rule是最簡(jiǎn)單的。我們可以重寫(xiě)isConflicting函數(shù)來(lái)實(shí)現(xiàn)一些更加復(fù)雜的控制,比如控制同時(shí)同類(lèi)型的Job最多只有指定的個(gè)數(shù)在運(yùn)行。但是我們要注意,isConflicting方法不能過(guò)于復(fù)雜。一旦一個(gè)Job的rule與其他Job的rule有沖突,isConflicting方法會(huì)調(diào)用很多次。如果其中的計(jì)算過(guò)于復(fù)雜,會(huì)影響整體的性能。
根據(jù)需要執(zhí)行Job 由于我們有的Job有可能不是立即執(zhí)行的,在有些情況下,等到該Job準(zhǔn)備執(zhí)行的時(shí)候,該Job所要執(zhí)行的任務(wù)已經(jīng)沒(méi)有意義了。這時(shí),我們可以使用Job的 shouldSchedule()和shouldRun()來(lái)避免Job的運(yùn)行。在我們定義一個(gè)Job時(shí),我們可以重載shouldSchedule和 shouldRun方法。在這些方法中,我們可以檢查Job運(yùn)行的一些先決條件,如果這些條件不滿(mǎn)足,我們就可以返回false。JobManager在安排Job運(yùn)行時(shí),它會(huì)先調(diào)用該Job的shouldSchedule方法,如果返回為false,JobManager就不會(huì)再安排這個(gè)Job運(yùn)行了。同樣,JobManager在真正啟動(dòng)一個(gè)線(xiàn)程運(yùn)行一個(gè)Job前,它會(huì)調(diào)用該Job的shouldRun方法,如果返回false,它不再運(yùn)行這個(gè) Job。在下面的例子中,我們希望啟動(dòng)一個(gè)Job在十秒鐘之后更新文本框中的內(nèi)容。為了保證我們的Job運(yùn)行時(shí)是有意義的,我們需要確保我們要更新的文本框沒(méi)有被銷(xiāo)毀,我們重載了shouldSchedule和shouldRun方法。
Text text = new Text(parent,SWT.NONE);
UIJob refreshJob = new UIJob(“更新界面”){
public IStatus runInUIThread(IProgressMonitor monitor) {
text.setText(“新文本”);
return Status.OK_STATUS;
}
public boolean shouldSchedule(){
return !text.isDisposed();
}
public boolean shouldRun(){
return !text.isDisposed();
}
};
refreshJob.schedule(10000);
在UI線(xiàn)程中涉及長(zhǎng)時(shí)間處理的任務(wù) 我們經(jīng)常碰到這樣一種情況:用戶(hù)操作菜單或者按鈕會(huì)觸發(fā)查詢(xún)大量數(shù)據(jù),數(shù)據(jù)查詢(xún)完后更新表格等界面元素。用戶(hù)點(diǎn)擊菜單或者按鈕所觸發(fā)的處理程序一般處于UI 線(xiàn)程,為了避免阻塞UI,我們必須把數(shù)據(jù)查詢(xún)等費(fèi)時(shí)的工作放到單獨(dú)的Job中執(zhí)行,一旦數(shù)據(jù)查詢(xún)完畢,我們又必須更新界面,這時(shí)我們又需要使用UI線(xiàn)程進(jìn)行處理。下面是處理這種情況的示例代碼:
button.addSelectionListener(new SelectionListener(){
public void widgetSelected(SelectionEvent e){
perform();
}
public void widgetDefaultSelected(SelectionEvent e){
perform();
}
private void perform(){
Job job = new Job(“獲取數(shù)據(jù)”){
protected IStatus run(IProgressMonitor monitor){
// 在此添加獲取數(shù)據(jù)的代碼
Display.getDefault().asyncExec(new Runnable(){
public void run(){
// 在此添加更新界面的代碼
}
});
}
};
job.schedule();
}
});
延時(shí)執(zhí)行Job,避免無(wú)用的Job運(yùn)行 我們經(jīng)常需要根據(jù)選中的對(duì)象刷新我們部分的界面元素。如果我們連續(xù)很快的改變選擇,而每次刷新界面涉及到的區(qū)域比較大時(shí),界面會(huì)出現(xiàn)閃爍。從用戶(hù)的角度來(lái)說(shuō),我們很快的改變選擇,希望看到的只是最后選中的結(jié)果,中間的界面刷新都是不必要的。
在Jface 中,StructuredViewer提供了addPostSelectionChangedListener方法。如果我們使用這個(gè)方法監(jiān)聽(tīng) selectionChanged事件,當(dāng)用戶(hù)一直按著方向鍵改變選中時(shí),我們只會(huì)收到一個(gè)selectionChanged事件。這樣我們可以避免過(guò)度的刷新界面。
實(shí)際上,Jface中就是通過(guò)延時(shí)執(zhí)行Job來(lái)實(shí)現(xiàn)這一功能的。我們也可以自己實(shí)現(xiàn)類(lèi)似功能:
private final static Object UPDATE_UI_JOBFAMILY = new Object();
tableviewer. addSelectionChangedListener (new ISelectionChangedListener (){
public void selectionChanged(SelectionChangedEvent event){
Job.getJobManager().cancel(UPDATE_UI_JOBFAMILY);
new UIJob("更新界面") {
protected IStatus runInUIThread (IProgressMonitor monitor) {
//更新界面
return Status.OK_STATUS;
}
public boolean belongsTo(Object family){
return family== UPDATE_UI_JOBFAMILY;
}
}.schedule(500);
}
});
首先,我們需要將界面更新的代碼放到一個(gè)UIJob中,同時(shí)我們將Job延時(shí)500毫秒執(zhí)行(我們可以根據(jù)需要改變延時(shí)的時(shí)間)。如果下一個(gè) selectionChanged事件很快到來(lái),我們的調(diào)用Job.getJobManager().cancel (UPDATE_UI_JOBFAMILY)將以前未運(yùn)行的Job取消,這樣只有最后一個(gè)Job會(huì)真正運(yùn)行。
在UI線(xiàn)程中等待非UI線(xiàn)程的結(jié)束 有時(shí),我們?cè)赨I線(xiàn)程中需要等待一個(gè)非UI線(xiàn)程執(zhí)行完,我們才能繼續(xù)執(zhí)行。例如,我們?cè)赨I線(xiàn)程中要顯示某些數(shù)據(jù),但是這些數(shù)據(jù)又需要從數(shù)據(jù)庫(kù)或者遠(yuǎn)程網(wǎng)絡(luò)獲取。于是,我們會(huì)啟動(dòng)一個(gè)非UI的線(xiàn)程去獲取數(shù)據(jù)。而我們的UI線(xiàn)程必須要等待這個(gè)非UI線(xiàn)程執(zhí)行完成,我們才能繼續(xù)執(zhí)行。當(dāng)然,一種簡(jiǎn)單的實(shí)現(xiàn)方法是使用join。我們可以在UI線(xiàn)程中調(diào)用非UI線(xiàn)程的join方法,這樣我們就可以等待它執(zhí)行完了,我們?cè)倮^續(xù)。但是,這會(huì)有一個(gè)問(wèn)題。當(dāng)我們的UI線(xiàn)程等待時(shí),意味著我們的程序不會(huì)再響應(yīng)界面操作,也不會(huì)刷新。這樣,用戶(hù)會(huì)覺(jué)得我們的程序象死了一樣沒(méi)有反應(yīng)。這時(shí),我們可以使用ModalContext 類(lèi)。你可以將你要執(zhí)行的獲取數(shù)據(jù)的任務(wù)用ModalContext的run方法來(lái)運(yùn)行(如下)。ModalContext會(huì)將你的任務(wù)放到一個(gè)獨(dú)立的非 UI線(xiàn)程中執(zhí)行,并且等待它執(zhí)行完再繼續(xù)執(zhí)行。與join方法不同的是,ModalContext在等待時(shí)不會(huì)停止UI事件的處理。這樣我們的程序就不會(huì)沒(méi)有響應(yīng)了。
try {
ModalContext.run(new IRunnableWithProgress(){
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
/*需要在非UI線(xiàn)程中執(zhí)行的代碼*/
ModalContext.checkCanceled(monitor);
}
}, true, new NullProgressMonitor(), Display.getCurrent());
} catch (InvocationTargetException e) {
} catch (InterruptedException e) {
}
針對(duì)相關(guān)聯(lián)的Job統(tǒng)一進(jìn)行處理 有時(shí),我們需要對(duì)相關(guān)聯(lián)的Job一起處理。例如需要同時(shí)取消這些Job,或者等待所有這些Job結(jié)束。這時(shí)我們可以使用Job Family。對(duì)于相關(guān)聯(lián)的Job,我們可以將它們?cè)O(shè)置成同一個(gè)Job Family。我們需要重載Job的belongsTo方法以設(shè)置一個(gè)Job的Job Family。
Private Object MY_JOB_FAMILY = new Object();
Job job = new Job(“Job Name”){
protected IStatus run(IProgressMonitor monitor) {
// 在這里添加你的任務(wù)代碼
return Status.OK_STATUS;
}
public boolean belongsTo(Object family){
return MY_JOB_FAMILY.equals(family);
}
};
我們可以使用JobManager的一系列方法針對(duì)Job Family進(jìn)行操作:
Job.getJobManager().cancel(MY_JOB_FAMILY); //取消所有屬于MY_JOB_FAMILY的所有Job
Job.getJobManager().join(MY_JOB_FAMILY); //等待屬于MY_JOB_FAMILY的所有Job結(jié)束
Job.getJobManager().sleep(MY_JOB_FAMILY); //將所有屬于MY_JOB_FAMILY的Job轉(zhuǎn)入睡眠狀態(tài)
Job.getJobManager().wakeup(MY_JOB_FAMILY); //將所有屬于MY_JOB_FAMILY的Job喚醒
線(xiàn)程死鎖的調(diào)試和解決技巧
一旦我們使用了線(xiàn)程,我們的程序中就有可能有死鎖的發(fā)生。一旦發(fā)生死鎖,我們發(fā)生死鎖的線(xiàn)程會(huì)沒(méi)有響應(yīng),導(dǎo)致我們程序性能下降。如果我們的UI線(xiàn)程發(fā)生了死鎖,我們的程序會(huì)沒(méi)有響應(yīng),必須要重啟程序。所以在我們多線(xiàn)程程序開(kāi)發(fā)中,發(fā)現(xiàn)死鎖的情況,解決死鎖問(wèn)題對(duì)提高我們程序的穩(wěn)定性和性能極為重要。
如果我們發(fā)現(xiàn)程序運(yùn)行異常(例如程序沒(méi)有響應(yīng)),我們首先要確定是否發(fā)生了死鎖。通過(guò)下面這些步驟,我們可以確定是否死鎖以及死鎖的線(xiàn)程:
在Eclipse中以Debug模式運(yùn)行程序 執(zhí)行響應(yīng)的測(cè)試用例重現(xiàn)問(wèn)題 在Eclipse的Debug View中選中主線(xiàn)程(Thread[main]),選擇菜單Run->Suspend。這時(shí)Eclipse會(huì)展開(kāi)主線(xiàn)程的函數(shù)調(diào)用棧,我們就可以看到當(dāng)前主線(xiàn)程正在執(zhí)行的操作。 通常,Eclipse在等待用戶(hù)的操作,它的函數(shù)調(diào)用棧會(huì)和以下類(lèi)似:
圖片示例
如果主線(xiàn)程發(fā)生死鎖,函數(shù)調(diào)用棧的最上層一般會(huì)是你自己的函數(shù)調(diào)用,你可以查看一下你當(dāng)前的函數(shù)調(diào)用以確定主線(xiàn)程在等待什么 使用同樣的方法查看其他線(xiàn)程,特別是那些等待UI線(xiàn)程的線(xiàn)程 我們需要找出當(dāng)前線(xiàn)程相互的等待關(guān)系,以便找出死鎖的原因。我們找出死鎖的線(xiàn)程后就可以針對(duì)不同情況進(jìn)行處理:
減小鎖的粒度,增加并發(fā)性 調(diào)整資源請(qǐng)求的次序 將需要等待資源的任務(wù)放到獨(dú)立的線(xiàn)程中執(zhí)行
Job使用中要注意的問(wèn)題
不 要在Job中使用Thread.sleep方法。如果你想要讓Job進(jìn)入睡眠狀態(tài),最好用Job的sleep方法。雖然,使用Thread.sleep和 Job的sleep方法達(dá)到的效果差不多,但是它們實(shí)現(xiàn)的方式完全不同,對(duì)系統(tǒng)的影響也不一樣。我們知道Eclipse中Job是由Eclipse的 JobManager來(lái)管理的。如果我們調(diào)用Job的sleep方法,JobManager會(huì)將Job轉(zhuǎn)入睡眠狀態(tài),與其對(duì)應(yīng)的線(xiàn)程也會(huì)重新放入線(xiàn)程池等 待運(yùn)行其他Job。而如果我們?cè)贘ob中直接調(diào)用Thread.sleep方法,它會(huì)直接使運(yùn)行Job的線(xiàn)程進(jìn)入睡眠狀態(tài),其他Job就不可能重用這個(gè)線(xiàn) 程了。同時(shí),雖然運(yùn)行該Job的線(xiàn)程進(jìn)入了睡眠狀態(tài),Job的狀態(tài)還是Running(運(yùn)行狀態(tài)),我們也不能用Job的wakeup方法喚醒該Job了 Job 的取消。一般我們會(huì)直觀的認(rèn)為,一旦調(diào)用Job的cancel方法,Job就會(huì)停止運(yùn)行。實(shí)際上,這并不一定正確,當(dāng)Job處于不同的狀態(tài)時(shí),我們調(diào)用 Job的cancel方法所起的效果是不同的。當(dāng)Job在WAITING狀態(tài)和SLEEPING狀態(tài)時(shí),一旦我們調(diào)用cancel方法, JobManager會(huì)將Job直接從等待運(yùn)行的隊(duì)列中刪除,Job不會(huì)再運(yùn)行了,這時(shí)cancel方法會(huì)返回true。但是如果Job正在運(yùn)行, cancel方法調(diào)用并不會(huì)立即終止Job的運(yùn)行,它只會(huì)設(shè)定一個(gè)標(biāo)志,指明這個(gè)Job已經(jīng)被取消了。我們可以使用Job的run方法傳入的參數(shù) IProgressMonitor monitor,這個(gè)參數(shù)的isCanceled方法會(huì)返回Job是否被取消的狀態(tài)。如果需要,我們必須在我們的代碼的適當(dāng)位置檢查Job是否被取消的標(biāo) 志,作出適當(dāng)?shù)捻憫?yīng)。另外,由于調(diào)用Job的cancel方法不一定立即終止Job,如果我們需要等待被取消的Job運(yùn)行完再執(zhí)行,我們可以用如下代碼: if (!job.cancel())
job.join();
Join方法的使用。由于join方法會(huì)導(dǎo)致一個(gè)線(xiàn)程等待另一個(gè)線(xiàn)程,一旦等待線(xiàn)程中擁有一個(gè)被等待線(xiàn)程所需要的鎖,就會(huì)產(chǎn)生死鎖。當(dāng)我們的線(xiàn)程中需要用到同步時(shí),這種死鎖的情況非常容易出現(xiàn),所以我們使用join時(shí)必須非常小心,盡量以其他方法替代。 避 免過(guò)時(shí)的Job造成的錯(cuò)誤。由于我們啟動(dòng)的線(xiàn)程并不一定是馬上執(zhí)行的,當(dāng)我們的Job開(kāi)始運(yùn)行時(shí),情況可能發(fā)生了變化。我們?cè)贘ob的處理代碼中要考慮到 這些情況。一種典型的情況是,我們?cè)趩?dòng)一個(gè)對(duì)話(huà)框或者初始化一個(gè)ViewPart時(shí),我們會(huì)啟動(dòng)一些 Job去完成一些數(shù)據(jù)讀取的工作,一旦數(shù)據(jù)讀取結(jié)束,我們會(huì)啟動(dòng)新的UI Job更新相應(yīng)的UI。有時(shí),用戶(hù)在打開(kāi)對(duì)話(huà)框或者View后,馬上關(guān)閉了該對(duì)話(huà)框或者View。這時(shí)我們啟動(dòng)的線(xiàn)程并沒(méi)有被中斷,一旦在Job中再去更 新UI,就會(huì)出錯(cuò)。在我們的代碼中必須作相應(yīng)的處理。所以,我們?cè)诰€(xiàn)程中更新界面元素之前,我們必須先檢查相應(yīng)的控件是否已經(jīng)被dispose了 結(jié)束語(yǔ)
在我們進(jìn)行基于Eclipse的客戶(hù)端開(kāi)發(fā)時(shí),使用多線(xiàn)程可以大大的提供我們的程序并發(fā)處理能力,同時(shí)對(duì)于提高用戶(hù)體驗(yàn)也有很好的幫助。但是,多線(xiàn)程程序也有其不利的一面,我們也不要濫用線(xiàn)程:
首先,多線(xiàn)程程序會(huì)大大的提高我們程序的復(fù)雜度,使得我們的開(kāi)發(fā)和調(diào)試更加困難 其次,過(guò)多的線(xiàn)程容易引發(fā)死鎖、數(shù)據(jù)同步等并發(fā)問(wèn)題的發(fā)生 另外,由于線(xiàn)程創(chuàng)建和銷(xiāo)毀需要開(kāi)銷(xiāo),程序的整體性能可能因?yàn)檫^(guò)多線(xiàn)程的使用而下降 所以,我們?cè)谑褂镁€(xiàn)程時(shí)一定要謹(jǐn)慎。本文對(duì)Eclipse線(xiàn)程的討論,希望能對(duì)大家使用線(xiàn)程有所幫助。由于實(shí)際情況較為復(fù)雜,文中所提到的方法僅供參考,讀者對(duì)于不同的實(shí)際問(wèn)題需要進(jìn)行具體分析,從而找出最佳的解決方案。