




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、編寫多線程的 Java 應(yīng)用程序如何避免當(dāng)前編程中最常見的問題Alex Roetter (aroetterCS.STeton Data Systems 的軟件工程師2001 年 2 月Java Thread API 允許程序員編寫具有多處理機(jī)制優(yōu)點(diǎn)的應(yīng)用程序在后臺(tái)處理任務(wù)的同時(shí)保持用戶所需的交互感Alex Roetter 介紹了Java Thread API并概述多線程可能引起的問題以及常見問題的解決方案幾乎所有使用 AWT 或 Swing 編寫的畫圖程序都需要多線程但多線程程序會(huì)造成許多困難剛開始編程的開發(fā)者常常會(huì)發(fā)現(xiàn)他們被一些問題所折磨例如不正確的程序行為或死鎖在本文
2、中我們將探討使用多線程時(shí)遇到的問題并提出那些常見陷阱的解決方案線程是什么 一個(gè)程序或進(jìn)程能夠包含多個(gè)線程這些線程可以根據(jù)程序的代碼執(zhí)行相應(yīng)的指令多線程看上去似乎在并行執(zhí)行它們各自的工作就像在一臺(tái)計(jì)算機(jī)上運(yùn)行著多個(gè)處理機(jī)一樣在多處理機(jī)計(jì)算機(jī)上實(shí)現(xiàn)多線程時(shí)它們確實(shí)可以并行工作和進(jìn)程不同的是線程共享地址空間也就是說多個(gè)線程能夠讀寫相同的變量或數(shù)據(jù)結(jié)構(gòu)編寫多線程程序時(shí)你必須注意每個(gè)線程是否干擾了其他線程的工作可以將程序看作一個(gè)辦公室如果不需要共享辦公室資源或與其他人交流所有職員就會(huì)獨(dú)立并行地工作某個(gè)職員若要和其他人交談當(dāng)且僅當(dāng)該職員在聽且他們兩說同樣的語言此外只有在復(fù)印機(jī)空閑且處于可用狀態(tài)沒有僅完成一
3、半的復(fù)印工作沒有紙張阻塞等問題時(shí)職員才能夠使用它在這篇文章中你將看到在 Java 程序中互相協(xié)作的線程就好像是在一個(gè)組織良好的機(jī)構(gòu)中工作的職員在多線程程序中線程可以從準(zhǔn)備就緒隊(duì)列中得到并在可獲得的系統(tǒng) CPU 上運(yùn)行操作系統(tǒng)可以將線程從處理器移到準(zhǔn)備就緒隊(duì)列或阻塞隊(duì)列中這種情況可以認(rèn)為是處理器掛起了該線程同樣Java 虛擬機(jī) (JVM 也可以控制線程的移動(dòng)在協(xié)作或搶先模型中從準(zhǔn)備就緒隊(duì)列中將進(jìn)程移到處理器中于是該線程就可以開始執(zhí)行它的程序代碼協(xié)作式線程模型允許線程自己決定什么時(shí)候放棄處理器來等待其他的線程程序開發(fā)員可以精確地決定某個(gè)線程何時(shí)會(huì)被其他線程掛起允許它們與對(duì)方有效地合作缺點(diǎn)在于某些惡
4、意或是寫得不好的線程會(huì)消耗所有可獲得的 CPU 時(shí)間導(dǎo)致其他線程饑餓 在搶占式線程模型中操作系統(tǒng)可以在任何時(shí)候打斷線程通常會(huì)在它運(yùn)行了一段時(shí)間就是所謂的一個(gè)時(shí)間片后才打斷它這樣的結(jié)果自然是沒有線程能夠不公平地長時(shí)間霸占處理器然而隨時(shí)可能打斷線程就會(huì)給程序開發(fā)員帶來其他麻煩同樣使用辦公室的例子假設(shè)某個(gè)職員搶在另一人前使用復(fù)印機(jī)但打印工作在未完成的時(shí)候離開了另一人接著使用復(fù)印機(jī)時(shí)該復(fù)印機(jī)上可能就還有先前那名職員留下來的資料搶占式線程模型要求線程正確共享資源協(xié)作式模型卻要求線程共享執(zhí)行時(shí)間由于 JVM 規(guī)范并沒有特別規(guī)定線程模型Java 開發(fā)員必須編寫可在兩種模型上正確運(yùn)行的程序在了解線程以及線程間
5、通訊的一些方面之后我們可以看到如何為這兩種模型設(shè)計(jì)程序線程和 Java 語言為了使用 Java 語言創(chuàng)建線程你可以生成一個(gè)Thread類或其子類的對(duì)象并給這個(gè)對(duì)象發(fā)送start(消息程序可以向任何一個(gè)派生自Runnable接口的類對(duì)象發(fā)送start(消息每個(gè)線程動(dòng)作的定義包含在該線程對(duì)象的run(方法中run 方法就相當(dāng)于傳統(tǒng)程序中的main(方法線程會(huì)持續(xù)運(yùn)行直到run(返回為止此時(shí)該線程便死了上鎖大多數(shù)應(yīng)用程序要求線程互相通信來同步它們的動(dòng)作在 Java 程序中最簡(jiǎn)單實(shí)現(xiàn)同步的方法就是上鎖為了防止同時(shí)訪問共享資源線程在使用資源的前后可以給該資源上鎖和開鎖假想給復(fù)印機(jī)上鎖任一時(shí)刻只有一個(gè)職員
6、擁有鑰匙若沒有鑰匙就不能使用復(fù)印機(jī)給共享變量上鎖就使得 Java 線程能夠快速方便地通信和同步某個(gè)線程若給一個(gè)對(duì)象上了鎖 就可以知道沒有其他線程能夠訪問該對(duì)象即使在搶占式模型中其他線程也不能夠訪問此對(duì)象直到上鎖的線程被喚醒完成工作并開鎖那些試圖訪問一個(gè)上鎖對(duì)象的線程通常會(huì)進(jìn)入睡眠狀態(tài)直到上鎖的線程開鎖一旦鎖被打開這些睡眠進(jìn)程就會(huì)被喚醒并移到準(zhǔn)備就緒隊(duì)列中在 Java 編程中所有的對(duì)象都有鎖線程可以使用synchronized關(guān)鍵字來獲得鎖在任一時(shí)刻對(duì)于給定的類的實(shí)例方法或同步的代碼塊只能被一個(gè)線程執(zhí)行這是因?yàn)榇a在執(zhí)行之前要求獲得對(duì)象的鎖繼續(xù)我們關(guān)于復(fù)印機(jī)的比喻為了避免復(fù)印沖突我們可以簡(jiǎn)單地對(duì)
7、復(fù)印資源實(shí)行同步如同下列的代碼例子任一時(shí)刻只允許一位職員使用復(fù)印資源通過使用方法在Copier對(duì)象中來修改復(fù)印機(jī)狀態(tài)這個(gè)方法就是同步方法只有一個(gè)線程能夠執(zhí)行一個(gè)Copier對(duì)象中同步代碼因此那些需要使用Copier 對(duì)象的職員就必須排隊(duì)等候class CopyMachine public synchronized void makeCopies(Document d, int nCopies /only one thread executes this at a timepublic void loadPaper( /multiple threads could access this at
8、once!synchronized(this /only one thread accesses this at a time/feel free to use shared resources, overwrite members, etc.Fine-grain 鎖在對(duì)象級(jí)使用鎖通常是一種比較粗糙的方法為什么要將整個(gè)對(duì)象都上鎖而不允許其他線程短暫地使用對(duì)象中其他同步方法來訪問共享資源如果一個(gè)對(duì)象擁有多個(gè)資源就不需要只為了讓一個(gè)線程使用其中一部分資源就將所有線程都鎖在外面由于每個(gè)對(duì)象都有鎖可以如下所示使用虛擬對(duì)象來上鎖class FineGrainLock MyMemberClass x, y
9、;Object xlock = new Object(, ylock = new Object(;public void foo( synchronized(xlock /access x here/do something here - but don't use shared resourcessynchronized(ylock /access y herepublic void bar( synchronized(this /access both x and y here/do something here - but don't use shared resourc
10、es若為了在方法級(jí)上同步不能將整個(gè)方法聲明為synchronized關(guān)鍵字它們使用的是成員鎖而不是 synchronized 方法能夠獲得的對(duì)象級(jí)鎖信號(hào)量通常情況下可能有多個(gè)線程需要訪問數(shù)目很少的資源假想在服務(wù)器上運(yùn)行著若干個(gè)回答客戶端請(qǐng)求的線程這些線程需要連接到同一數(shù)據(jù)庫但任一時(shí)刻只能獲得一定數(shù)目的數(shù)據(jù)庫連接你要怎樣才能夠有效地將這些固定數(shù)目的數(shù)據(jù)庫連接分配給大量的線程一種控制訪問一組資源的方法除了簡(jiǎn)單地上鎖之外就是使用眾所周知的信號(hào)量計(jì)數(shù) (counting semaphore信號(hào)量計(jì)數(shù)將一組可獲得資源的管理封裝起來信號(hào)量是在簡(jiǎn)單上鎖的基礎(chǔ)上實(shí)現(xiàn)的,相當(dāng)于能令線程安全執(zhí)行并初始化為可用資源
11、個(gè)數(shù)的計(jì)數(shù)器例如我們可以將一個(gè)信號(hào)量初始化為可獲得的數(shù)據(jù)庫連接個(gè)數(shù)一旦某個(gè)線程獲得了信號(hào)量可獲得的數(shù)據(jù)庫連接數(shù)減一線程消耗完資源并釋放該資源時(shí)計(jì)數(shù)器就會(huì)加一當(dāng)信號(hào)量控制的所有資源都已被占用時(shí)若有線程試圖訪問此信號(hào)量則會(huì)進(jìn)入阻塞狀態(tài)直到有可用資源被釋放信號(hào)量最常見的用法是解決消費(fèi)者生產(chǎn)者問題當(dāng)一個(gè)線程進(jìn)行工作時(shí)若另外一個(gè)線程訪問同一共享變量就可能產(chǎn)生此問題消費(fèi)者線程只能在生產(chǎn)者線程完成生產(chǎn)后才能夠訪問數(shù)據(jù)使用信號(hào)量來解決這個(gè)問題就需要?jiǎng)?chuàng)建一個(gè)初始化為零的信號(hào)量從而讓消費(fèi)者線程訪問此信號(hào)量時(shí)發(fā)生阻塞每當(dāng)完成單位工作時(shí)生產(chǎn)者線程就會(huì)向該信號(hào)量發(fā)信號(hào)釋放資源每當(dāng)消費(fèi)者線程消費(fèi)了單位生產(chǎn)結(jié)果并需要新的數(shù)
12、據(jù)單元時(shí)它就會(huì)試圖 再次獲取信號(hào)量因此信號(hào)量的值就總是等于生產(chǎn)完畢可供消費(fèi)的數(shù)據(jù)單元數(shù)這種方法比采用消費(fèi)者線程不停檢查是否有可用數(shù)據(jù)單元的方法要高效得多因?yàn)橄M(fèi)者線程醒來后倘若沒有找到可用的數(shù)據(jù)單元就會(huì)再度進(jìn)入睡眠狀態(tài)這樣的操作系統(tǒng)開銷是非常昂貴的盡管信號(hào)量并未直接被 Java 語言所支持卻很容易在給對(duì)象上鎖的基礎(chǔ)上實(shí)現(xiàn)一個(gè)簡(jiǎn)單的實(shí)現(xiàn)方法如下所示class Semaphore private int count;public Semaphore(int n this.count = n;public synchronized void acquire( while(count = 0 try
13、wait(; catch (InterruptedException e /keep tryingcount-;public synchronized void release( count+;notify(; /alert a thread that's blocking on this semaphore常見的上鎖問題不幸的是使用上鎖會(huì)帶來其他問題讓我們來看一些常見問題以及相應(yīng)的解決方法死鎖死鎖是一個(gè)經(jīng)典的多線程問題因?yàn)椴煌木€程都在等待那些根本不可能被釋放的鎖從而導(dǎo)致所有的工作都無法完成假設(shè)有兩個(gè)線程分別代表兩個(gè)饑餓的人他們必須共享刀叉并輪流吃飯他們都需要獲得兩個(gè)鎖共享刀和共享叉
14、的鎖假如線程 "A" 獲得了刀而線程 "B" 獲得了叉線 程 A 就會(huì)進(jìn)入阻塞狀態(tài)來等待獲得叉而線程 B 則阻塞來等待 A 所擁有的刀這只是人為設(shè)計(jì)的例子但盡管在運(yùn)行時(shí)很難探測(cè)到這類情況卻時(shí)常發(fā)生雖然要探測(cè)或推敲各種情況是非常困難的 但只要按照下面幾條規(guī)則去設(shè)計(jì)系統(tǒng)就能夠避免死鎖問題 讓所有的線程按照同樣的順序獲得一組鎖這種方法消除了 X 和 Y 的擁有者分別等待對(duì)方的資源的問題 將多個(gè)鎖組成一組并放到同一個(gè)鎖下前面死鎖的例子中可以創(chuàng)建一個(gè)銀器對(duì)象的鎖于是在獲得刀或叉之前都必須獲得這個(gè)銀器的鎖 將那些不會(huì)阻塞的可獲得資源用變量標(biāo)志出來當(dāng)某個(gè)線程獲得銀器對(duì)
15、象的鎖時(shí)就可以通過 檢查變量來判斷是否整個(gè)銀器集合中的對(duì)象鎖都可獲得如果是它就可以獲得相關(guān)的鎖否則就要釋放掉銀器這個(gè)鎖并稍后再嘗試 最重要的是在編寫代碼前認(rèn)真仔細(xì)地設(shè)計(jì)整個(gè)系統(tǒng)多線程是困難的在開始編程之前詳細(xì) 設(shè)計(jì)系統(tǒng)能夠幫助你避免難以發(fā)現(xiàn)死鎖的問題Volatile 變量.volatile關(guān)鍵字是 Java 語言為優(yōu)化編譯器設(shè)計(jì)的以下面的代碼為例 class VolatileTest public void foo( boolean flag = false; if(flag /this could happen 一個(gè)優(yōu)化的編譯器可能會(huì)判斷出 if 部分的語句永遠(yuǎn)不會(huì)被執(zhí)行 就根本不會(huì)編譯這
16、部分的代碼 如果這個(gè)類被多線程訪問 flag 被前面某個(gè)線程設(shè)置之后 在它被 if 語 句測(cè)試之前 可以被其他線程重新設(shè)置 用 volatile 關(guān)鍵字來聲明變量 就可以告訴 編譯器在編譯的時(shí)候 不需要通過預(yù)測(cè)變量值來優(yōu)化這部分的代碼 無法訪問的線程 有時(shí)候雖然獲取對(duì)象鎖沒有問題 IO 就是這類問題最好的例子 訪問 線程依然有可能進(jìn)入阻塞狀態(tài) 在 Java 編程中 當(dāng)線程因?yàn)閷?duì)象內(nèi)的 IO 調(diào)用而阻塞時(shí) 此對(duì)象應(yīng)當(dāng)仍能被其他線程 該對(duì)象通常有責(zé)任取消這個(gè)阻塞的 IO 操作 當(dāng)線程被阻塞時(shí) 造成阻塞調(diào)用的線程常常會(huì)令同步任務(wù)失敗 如 此對(duì)象也就相當(dāng)于被冷凍住了 其他的線程由于 果該對(duì)象的其他方法
17、也是同步的 不能獲得對(duì)象的鎖 就不能給此對(duì)象發(fā)消息 例如 取消 IO 操作 些阻塞調(diào)用 或確認(rèn)在一個(gè)用同步阻塞代碼的對(duì)象中存在非同步方法 必須確保不在同步代碼中包含那 盡管這種方法需要花費(fèi)一些注 意力來保證結(jié)果代碼安全運(yùn)行 但它允許在擁有對(duì)象的線程發(fā)生阻塞后 該對(duì)象仍能夠響應(yīng)其他線程 為不同的線程模型進(jìn)行設(shè)計(jì) 判斷是搶占式還是協(xié)作式的線程模型 取決于虛擬機(jī)的實(shí)現(xiàn)者 并根據(jù)各種實(shí)現(xiàn)而不同 因 此 Java 開發(fā)員必須編寫那些能夠在兩種模型上工作的程序 正如前面所提到的 在搶占式模型中線程可以在代碼的任何一個(gè)部分的中間被打斷 除非那 是一個(gè)原子操作代碼塊 原子操作代碼塊中的代碼段一旦開始執(zhí)行 就要
18、在該線程被換出處 理器之前執(zhí)行完畢 在 Java 編程中 分配一個(gè)小于 32 位的變量空間是一種原子操作 而 此外象 double 和 long 這兩個(gè) 64 位數(shù)據(jù)類型的分配就不是原子的 使用鎖來正確同步共享 資源的訪問 就足以保證一個(gè)多線程程序在搶占式模型下正確工作 而在協(xié)作式模型中 是否能保證線程正常放棄處理器 不掠奪其他線程的執(zhí)行時(shí)間 則完全 取決于程序員 調(diào)用 yield( 方法能夠?qū)?dāng)前的線程從處理器中移出到準(zhǔn)備就緒隊(duì)列中 另 一個(gè)方法則是調(diào)用 sleep( 方法 使線程放棄處理器 并且在 sleep 方法中指定的時(shí)間間隔 內(nèi)睡眠 正如你所想的那樣 將這些方法隨意放在代碼的某個(gè)地方
19、 并不能夠保證正常工作 如果線 程正擁有一個(gè)鎖 因?yàn)樗谝粋€(gè)同步方法或代碼塊中 則當(dāng)它調(diào)用 yield( 時(shí)不能夠釋放 這個(gè)鎖 這就意味著即使這個(gè)線程已經(jīng)被掛起 等待這個(gè)鎖釋放的其他線程依然不能繼續(xù)運(yùn) 行 為了緩解這個(gè)問題 最好不在同步方法中調(diào)用 yield 方法 將那些需要同步的代碼包在 一個(gè)同步塊中 里面不含有非同步的方法 并且在這些同步代碼塊之外才調(diào)用 yield 另外一個(gè)解決方法則是調(diào)用 wait( 方法 使處理器放棄它當(dāng)前擁有的對(duì)象的鎖 如果對(duì)象在 方法級(jí)別上使同步的 這種方法能夠很好的工作 因?yàn)樗鼉H僅使用了一個(gè)鎖 如果它使用 fine-grained 鎖 則 wait( 將無法放棄
20、這些鎖 此外 一個(gè)因?yàn)檎{(diào)用 wait( 方法而阻塞的 線程 只有當(dāng)其他線程調(diào)用 notifyAll( 時(shí)才會(huì)被喚醒 線程和 AWT/Swing 在那些使用 Swing 和/或 AWT 包創(chuàng)建 GUI 用戶圖形界面 的 Java 程序中 AWT 事 件句柄在它自己的線程中運(yùn)行 開發(fā)員必須注意避免將這些 GUI 線程與較耗時(shí)間的計(jì)算工 作綁在一起 因?yàn)檫@些線程必須負(fù)責(zé)處理用戶時(shí)間并重繪用戶圖形界面 換句話來說 一旦 GUI 線程處于繁忙 整個(gè)程序看起來就象無響應(yīng)狀態(tài) Swing 線程通過調(diào)用合適方法 通 知那些 Swing callback 例如 Mouse Listener 和 Action L
21、istener 這種方法意味著 listener 無論要做多少事情 都應(yīng)當(dāng)利用 listener callback 方法產(chǎn)生其他線程來完成此項(xiàng)工 作 目的便在于讓 listener callback 更快速返回 從而允許 Swing 線程響應(yīng)其他事件 如果一個(gè) Swing 線程不能夠同步運(yùn)行 響應(yīng)事件并重繪輸出 那怎么能夠讓其他的線程安 全地修改 Swing 的狀態(tài) 正如上面提到的 Swing callback 在 Swing 線程中運(yùn)行 因此 他們能修改 Swing 數(shù)據(jù)并繪到屏幕上 但是如果不是 Swing callback 產(chǎn)生的變化該怎么辦呢 使用一個(gè)非 Swing 線程來修改 Swing 數(shù)據(jù)是不安全的 Swing 提供了兩個(gè)方法來解決這個(gè)問題 invokeLater( 和 invokeAndWait( 為了修改 S
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 金屬工藝品設(shè)計(jì)中的消費(fèi)者行為研究考核試卷
- 通信設(shè)備在社區(qū)健康管理中的應(yīng)用考核試卷
- LM385呼吸燈技術(shù)解析
- 精神疾病的預(yù)防與控制
- 院前急救的轉(zhuǎn)運(yùn)與交接
- Pentoxifylline-d3-BL-191-d-sub-3-sub-生命科學(xué)試劑-MCE
- 湖北省2025年中考第三次模擬考試物理試卷(含答案)
- 國家開放大學(xué)電大教育學(xué)形考任務(wù)1234答案
- 高血壓腎病的臨床觀察
- 2025下半年石油石化行業(yè)油價(jià)回歸中性區(qū)間擁抱景氣改善的投資機(jī)會(huì)
- 敘事護(hù)理學(xué)智慧樹知到答案2024年中國人民解放軍海軍軍醫(yī)大學(xué)
- DB65-T 4747-2024 地表水自壓滴灌工程設(shè)計(jì)規(guī)范
- 2024小學(xué)語文教學(xué)及說課課件:六年級(jí)上冊(cè)《只有一個(gè)地球》
- 2023年甘肅蘭州中考滿分作文《喚起心中的勇敢》
- 國企文秘筆試題
- HG∕T 4380-2012 液-固微旋流分離器技術(shù)條件
- 《經(jīng)濟(jì)法基礎(chǔ)》全套教學(xué)課件
- 人教版高中數(shù)學(xué)知識(shí)點(diǎn)(全冊(cè)版)
- 成都市高新區(qū)五年級(jí)語文調(diào)考真題試卷
- 人教版一年級(jí)下冊(cè) 第六單元 小白船 教案+教學(xué)設(shè)計(jì)+素材
- 山東省棗莊市薛城區(qū)2023-2024學(xué)年八年級(jí)下學(xué)期7月期末歷史試題(無答案)
評(píng)論
0/150
提交評(píng)論