viewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
new Job("go to db") {
protected IStatus run(IProgressMonitor monitor) {
//do expensive work here
return Status.OK_STATUS;
}
}.schedule();
}
});
清單 3 的開發(fā)人員知道這個(gè)操作很耗時(shí),把它放到一個(gè)后臺(tái) Job 中去做。但是問題在于開發(fā)人員沒有預(yù)料到 用戶可能在收件箱中選擇一條消息后會(huì)按住鍵盤上的向下箭頭持續(xù)幾秒鐘。 一般在這種情況下,每個(gè)選擇的改變會(huì)導(dǎo)致執(zhí)行一個(gè)新的后臺(tái) Job。很快,JobManager 會(huì)被 Job 填滿。
清單 4 提供了一個(gè)更好的選擇,不使用選擇處理程序, 而是用 postSelection 處理程序。JFace 為執(zhí)行事件接合 提供了 PostSelection 處理程序,從而使得后一個(gè)選擇成為您的應(yīng)用程序所接受的 選擇 —— 而不是您的應(yīng)用程序無法處理的一大堆選擇。 您可以認(rèn)為它忽略了過于嘈雜的細(xì)小事件,僅關(guān)注其中一些較大的。
清單 4. 僅對(duì)后一個(gè)選擇改變做響應(yīng)的選擇偵聽器
viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
new Job("go to db") {
protected IStatus run(IProgressMonitor monitor) {
//do expensive work here
return Status.OK_STATUS;
}
}.schedule();
}
};
處理磁盤 I/O
您一定聽說過:內(nèi)存讀寫以納秒計(jì),磁盤讀寫以毫秒計(jì)。 在我曾參與的絕大多數(shù) RCP 應(yīng)用程序中,瓶頸是 CPU,而不是磁盤 I/O。 但是,磁盤 I/O 仍然可能成為問題,您不應(yīng)忽視它。 RCP 應(yīng)用程序的一類常見問題是對(duì)磁盤的低效率映象讀取。
我一般使用 Sysinternals Process Monitor 考察一個(gè)應(yīng)用程序如何使用文件。 舉例而言,圖 5 說明可以容易地識(shí)別對(duì)未緩沖文件的讀。
圖 5. Process monitor 顯示未緩沖讀取 I/O
如果您逐行閱讀圖 5,您會(huì)發(fā)現(xiàn)正在讀取一個(gè)名為 big.txt 的文件。 后一列說明對(duì)該文件每次只讀取一個(gè)字節(jié)。清單 5 說明了導(dǎo)致這種情況的代碼:
清單 5. 未緩沖的 I/O(不要這么做)
InputStream in = new FileInputStream(args[0]);
int c;
while ((c = in.read()) != -1) {
//stuff characters in buffer, etc
}
我的 ThinkPad T60p 筆記本硬盤速度是 7200RPM,它讀取一個(gè) 7MB 的文件需要 用 24 秒。如果用 BufferedInputStream,時(shí)間將減少到 350 毫秒。 絕大多數(shù)的此類提速可歸功于更好地利用硬盤?赡苣慕^大多數(shù)程序不會(huì)有這么巨大的效果, 但是仍然值得用象 BufferedInputStream 一樣的緩沖流來解決未緩沖 I/O 問題。
我在 RCP 應(yīng)用程序中觀察到的另一個(gè)問題被我稱為映象燃燒, 當(dāng)從磁盤頻繁讀取一個(gè)映象然后丟棄時(shí)該問題會(huì)發(fā)生。根據(jù)頻繁程度,該映象可能好進(jìn)入緩存。
線程和 Eclipse 作業(yè)
應(yīng)用程序應(yīng)該充分利用計(jì)算機(jī)資源。 為了優(yōu)化地使用 CPU,應(yīng)用程序所擁有的線程數(shù)目應(yīng)與處理程序計(jì)數(shù)和線程工作類型相關(guān)。 每個(gè) Java 線程都關(guān)聯(lián)著一定數(shù)量的本地內(nèi)存,線程切換上下文時(shí)會(huì)導(dǎo)致一些 CPU 耗費(fèi), 所以并不是線程越多越好。
我經(jīng)常在客戶端應(yīng)用程序中看到對(duì)線程的錯(cuò)誤使用。對(duì)于那些曾可能阻塞 UI 線程的長時(shí)間運(yùn)行操作, 用一些線程來做是好事,但是在基于 RCP 的應(yīng)用程序中,Job(幾乎)應(yīng)該總優(yōu)先于線程。
您還應(yīng)小心不要讓 JobManager 被 Job 填滿, 因?yàn)檫@樣的話,它的默認(rèn)工作者池會(huì)無限增長下去。
Job 類似于 Runnable:它們描述 task,而不是所運(yùn)行的線程。Job(并不令人驚訝)由 JobManager 管理,它維持著一個(gè)工作者池。即很多 Job 可以被一個(gè)工作者池管理。Job 相比于線程所具有的另一個(gè)大優(yōu)勢是 Job 可以從外部(out-of-the-box)進(jìn)行記錄。您可以設(shè)置一些標(biāo)識(shí)并運(yùn)行應(yīng)用程序,JobManager 會(huì)告訴您每個(gè) Job 何時(shí)被創(chuàng)建、安排、運(yùn)行以及完成。JobManager 還會(huì)告訴您它是如何管理工作者池。 當(dāng)您試圖了解何時(shí)執(zhí)行后臺(tái) Job 以及它們的運(yùn)行需要多長時(shí)間時(shí),這會(huì)是一個(gè)巨大的優(yōu)勢。
要啟用這項(xiàng)支持,將 清單 6 的內(nèi)容添加到一個(gè)文件。(此信息可以在org.eclipse.core.jobs 包的 .options 文件中找到。) 然后啟動(dòng)您的 RCP 應(yīng)用程序,需要有 -debug Path_to_debug_file .
清單 6. 啟用 Job 調(diào)試信息
# Prints debug information on running background jobs
org.eclipse.core.jobs/jobs=true
# Includes current date and time in job debug information
org.eclipse.core.jobs/jobs/timing=true
# Computes location of error on mismatched IJobManager.beginRule/endRule
org.eclipse.core.jobs/jobs/beginend=true
# Pedantic assertion checking on locks and deadlock reporting
org.eclipse.core.jobs/jobs/locks=true
# Throws an IllegalStateException when deadlock occurs
org.eclipse.core.jobs/jobs/errorondeadlock=true
# Debug shutdown behaviour
org.eclipse.core.jobs/jobs/shutdown=true
Job 還支持高級(jí)計(jì)劃規(guī)則。 您可以創(chuàng)建簡單的或復(fù)雜的計(jì)劃規(guī)則,支配 Job 的運(yùn)行時(shí)機(jī)。清單 7 說明了一種方式,您可以創(chuàng)建一條規(guī)則,禁止 兩個(gè) Job 同時(shí)運(yùn)行:
清單 7. 禁止兩個(gè) Job 同時(shí)運(yùn)行
ISchedulingRule onlyOne = new ISchedulingRule() {
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
};
Job job1 = new LongRunningJob();
Job job2 = new LongRunningJob();
job1.setRule(onlyOne);
job2.setRule(onlyOne);
job1.schedule();
job2.schedule();
return onlyOne;
清單 7 中的代碼能夠工作,是因?yàn)橛?jì)劃規(guī)則的 isConflicting() 方法在 Job 運(yùn)行前被調(diào)用。當(dāng) job1 執(zhí)行時(shí),它 “擁有” 該規(guī)則。請參閱 ISchedulingRule 的實(shí)現(xiàn)。要獲得更多示例(請參閱 參考資料)。
java.util.Timer 也容易被錯(cuò)誤使用。 很多應(yīng)用程序會(huì)創(chuàng)建幾個(gè) Timer,每個(gè) Timer 創(chuàng)建一個(gè)專門的管理線程。典型地,每個(gè)應(yīng)用程序應(yīng)只創(chuàng)建一個(gè) Timer 并讓它管理多個(gè) java.util.TimerTask,但是很多相互孤立的開發(fā)人員會(huì)創(chuàng)建他們自己的 Timer,這只會(huì)浪費(fèi)更多的線程。在幾乎所有的 RCP 應(yīng)用程序案例中,java.util.Timer 可以被 Job 取代,計(jì)劃在未來某個(gè)時(shí)間執(zhí)行。
如果您所運(yùn)行的 JVM 版本高于 1.4,您能從 java.util.concurrent Executor、ScheduledThreadPoolExecutor 和 Task 得到大量相同好處。java.util.Timer 類并未受到輕視,但不管出于何種目的, 它都可由 ScheduledExecutorService替代。