Eros Live
          Find the Way
          posts - 15,comments - 0,trackbacks - 0
          Single Threaded Execution是指“以1個(gè)線程執(zhí)行”的意思。就像細(xì)獨(dú)木橋只允許一個(gè)人通行一樣,這個(gè)Pattern用來(lái)限制同時(shí)只讓一個(gè)線程運(yùn)行。

          Single Threaded Execution將會(huì)是多線程程序設(shè)計(jì)的基礎(chǔ)。務(wù)必要學(xué)好。

          Single Threaded Execution有時(shí)候也被稱為Critical Section(臨界區(qū))。

          Single Threaded Execution是把視點(diǎn)放在運(yùn)行的線程(過(guò)橋的人)上所取的名字,而Critical Section則是把視點(diǎn)放在執(zhí)行的范圍(橋身)上所取的名字。

          范例程序1:不使用Single Threaded Execution Pattern的范例

          首先,我們先來(lái)看一個(gè)應(yīng)該要使用Single Threaded Execution Pattern而沒(méi)有使用和程序范例。這個(gè)程序的用意是要實(shí)際體驗(yàn)多線程無(wú)法正確執(zhí)行的程序,會(huì)發(fā)生什么現(xiàn)象。

          模擬3個(gè)人頻繁地經(jīng)過(guò)一個(gè)只能容許一個(gè)人經(jīng)過(guò)的門。當(dāng)人通過(guò)門的時(shí)候,這個(gè)程序會(huì)在計(jì)數(shù)器中遞增通過(guò)的人數(shù)。另外,還會(huì)記錄通過(guò)的人的“姓名與出生地”

          表1-1 類一覽表
          --------------------------------------------------------------
          ?名稱??   ??????????????????? 說(shuō)明
          --------------------------------------------------------------
          Main??????????  創(chuàng)建一個(gè)門,并操作3個(gè)人不斷地穿越門的類
          Gate?  ???????? 表示門的類,當(dāng)人經(jīng)過(guò)時(shí)會(huì)記錄下姓名與出身地
          UserThread?????? 表示人的類,只負(fù)責(zé)處理不斷地在門間穿梭通過(guò)
          --------------------------------------------------------------

          Main類

          Main類(List 1-1)用來(lái)創(chuàng)建一個(gè)門(Gate),并讓3個(gè)人(UserThread)不斷通過(guò)。創(chuàng)建Gate對(duì)象的實(shí)例,并將這個(gè)實(shí)例丟到UserThread類的構(gòu)造器作為參數(shù),告訴人這個(gè)對(duì)象“請(qǐng)通過(guò)這個(gè)門”。

          有下面3個(gè)人會(huì)通過(guò)這個(gè)門:

          Alice - Alaska
          Bobby - Brazil
          Chris - Canada

          為了便于對(duì)應(yīng)兩者之間的關(guān)系,筆者在此故意將姓名與出生地設(shè)成相同的開頭字母。

          在上線程中,先創(chuàng)建3個(gè)UserThread類的實(shí)例,并以start方法啟動(dòng)這些線程。

          List 1-1 Main.java
          --------------------------------------
          public class Main {
          ??? public static void main(String[] args) {
          ??????? System.out.println("Testing Gate, hit CTRL+C to exit.");
          ??????? Gate gate = new Gate();
          ??????? new UserThread(gate, "Alice", "Alaska").start();
          ??????? new UserThread(gate, "Bobby", "Brazil").start();
          ??????? new UserThread(gate, "Chris", "Canada").start();
          ??? }
          }
          --------------------------------------

          并非線程安全的(thread-safe)的Gate類

          Gate類(List 1-2)表示人所要通過(guò)的門。

          counter字段表示目前已經(jīng)通過(guò)這道門的“人數(shù)”。name字段表示通過(guò)門的行人的“姓名”,而address字段則表示通過(guò)者的“出生地”

          pass是穿越這道門時(shí)使用的方法。在這個(gè)方法中,會(huì)將表示通過(guò)人數(shù)的counter字段的值遞增1,并將參數(shù)中傳入行人的姓名與出生地,分別拷貝到name字段與address字段中。

          this.name = name;

          toString方法,會(huì)以字符串的形式返回現(xiàn)在門的狀態(tài)。使用現(xiàn)在的counter、name、address各字段的值,創(chuàng)建字符串。

          check方法,用來(lái)檢查現(xiàn)在門的狀態(tài)(最后通過(guò)的行人的記錄數(shù)據(jù))是否正確。當(dāng)人的姓名(name)與出生地(address)第一個(gè)字符不相同時(shí),就斷定記錄是有問(wèn)題的。當(dāng)發(fā)現(xiàn)記錄有問(wèn)題時(shí),就顯示出下面的字符串:

          ****** BROKEN ******
          并接著調(diào)用toString方法顯示出現(xiàn)在門的狀態(tài)。

          這個(gè)Gate類,在單線程的時(shí)候可以正常運(yùn)行,但是在多線程下就無(wú)法正常執(zhí)行。List 1-2 的Gate類是缺乏安全性的類,并不是線程安全(thread-safe)的類。

          List 1-1 非線程安全的Gate類(Gate.java)

          ??? public class Gate {
          ??????? private int counter = 0;
          ??????? private String name = "Nobody";
          ??????? private String address = "Nowhere";
          ??????? public void pass(String name, String address) {
          ??????????? this.counter++;
          ??????????? this.name = name;
          ??????????? this.address = address;
          ??????????? check();
          ??????? }
          ??????? public String toString() {
          ??????????? return "No." + counter + ": " + name + ", " + address;
          ??????? }
          ??????? private void check() {
          ??????????? if (name.charAt(0) != address.charAt(0)) {
          ??????????????? System.out.println("***** BROKEN ***** " + toString());
          ??????????? }
          ??????? }
          ??? }

          UserThread類

          UserThread類(List 1-3)表示不斷穿越門的行人。這個(gè)類被聲明成Thread類的子類。

          List 1-3 UserThread.java

          ??? public class UserThread extends Thread {
          ??????? private final Gate gate;
          ??????? private final String myname;
          ??????? private final String myaddress;
          ??????? public UserThread(Gate gate, String myname, String myaddress) {
          ??????????? this.gate = gate;
          ??????????? this.myname = myname;
          ??????????? this.myaddress = myaddress;
          ??????? }
          ??????? public void run() {
          ??????????? System.out.println(myname + " BEGIN");
          ??????????? while (true) {
          ??????????????? gate.pass(myname, myaddress);
          ??????????? }
          ??????? }
          ??? }

          為什么會(huì)出錯(cuò)呢?

          這是因?yàn)镚ate類的pass方法會(huì)被多個(gè)線程調(diào)用的關(guān)系。pass方法是下面4行語(yǔ)句程序代碼所組成:

          this.counter++;
          this.name = name;
          this.address = address;
          check();

          為了在解說(shuō)的時(shí)候簡(jiǎn)單一點(diǎn),現(xiàn)在只考慮兩個(gè)線程(Alice和Bobby)。兩個(gè)線程調(diào)用pass方法時(shí),上面4行語(yǔ)句可能會(huì)是交錯(cuò)依次執(zhí)行。如果交錯(cuò)的情況是圖1-3這樣,那調(diào)用check方法的時(shí)候,name的值會(huì)是“Alice”,而address的值會(huì)是“Brazil”。這時(shí)就會(huì)顯示出 BROKEN了。

          圖1-3 線程Alice與線程Bobby調(diào)用pass方法時(shí)的執(zhí)行狀況
          -----------------------------------------------------------------------------------
          線程Alice?????????????? 線程Bobby?????????????? this.name的值??????? this.address的值
          -----------------------------------------------------------------------------------
          this.counter++???????? this.counter++
          ?????????????????????????????? this.name = name??????? "Bobby"??
          this.name = name?????????????????????????????????????? "Alice"
          this.address = address??????????????????????????????? "Alice"???????????? "Alaska"
          ????????????????????????????? this.address = address? "Alice"???????????? "Brazil"
          check()???????????????? ?check()???????????????????????? "Alice"???????????? "Brazil"
          ****** BROKEN ******
          -----------------------------------------------------------------------------------

          或者說(shuō)交錯(cuò)的情況如圖1-4所示,則調(diào)用check方法的時(shí)刻,name的值是"Bobby",而address的值會(huì)是"Alaska"。這個(gè)時(shí)候也會(huì)顯示出BROKEN。

          圖1-4 線程Alice與線程Bobby調(diào)用pass方法的執(zhí)行狀況
          ------------------------------------------------------------------------------------
          線程Alice????????????? 線程Bobby??????????????? this.name的值??????? this.address的值
          ------------------------------------------------------------------------------------
          this.counter++??????? this.counter++?
          this.name = name??????????????????????????????????????? "Alice"
          ???????????????????????????? this.name = name????????? ?"Bobby"
          ??????????????????????????? ?this.address = address??? "Bobby"????????????? "Brazil"
          this.address = address???????????????????????????????? "Bobby"????????????? "Alaska"
          check()???????????????? check()?????????????????????????? "Bobby"????????????? "Alaska"
          ****** BROKEN ******
          ------------------------------------------------------------------------------------

          上述哪一種情況,都使字段name與address的值出現(xiàn)非預(yù)期的結(jié)果。
          通常,線程不會(huì)去考慮其他的線程,而自己只會(huì)一直不停地跑下去。“線程Alice現(xiàn)在執(zhí)行到的位置正指定name結(jié)束,還沒(méi)有指定address的值”,而線程Bobby對(duì)此情況并不知情。

          范例程序1之所以會(huì)顯示出BROKEN,是因?yàn)榫€程并沒(méi)有考慮到其他線程,而將共享實(shí)例的字段改寫了。
          對(duì)于name字段來(lái)說(shuō),有兩個(gè)線程在比賽,贏的一方先將值改寫。對(duì)address來(lái)說(shuō),也有兩個(gè)線程在比賽誰(shuí)先將值改寫。像這樣子引發(fā)競(jìng)爭(zhēng)(race)的狀況,我們稱為race condition。有race condition的情況時(shí),就很難預(yù)測(cè)各字段的值了。


          以上是沒(méi)有使用Single Threaded Execution Pattern時(shí)所發(fā)生的現(xiàn)象。

          范例程序2:使用Single Threaded Execution Pattern的范例

          線程安全的Gate類
          List 1-4 是線程安全的Gate類。需要修改的有兩個(gè)地方,在pass方法與toString方法前面都加上synchronized。這樣Gate類就成為線程安全的類了。

          List 1-4 線程安全的Gate類(Gate.java)

          ??? public class Gate {
          ??????? private int counter = 0;
          ??????? private String name = "Nobody";
          ??????? private String address = "Nowhere";
          ??????? public synchronized void pass(String name, String address) {
          ??????????? this.counter++;
          ??????????? this.name = name;
          ??????????? this.address = address;
          ??????????? check();
          ??????? }
          ??????? public synchronized String toString() {
          ??????????? return "No." + counter + ": " + name + ", " + address;
          ??????? }
          ??????? private void check() {
          ??????????? if (name.charAt(0) != address.charAt(0)) {
          ??????????????? System.out.println("***** BROKEN ***** " + toString());
          ??????????? }
          ??????? }
          ??? }

          synchronized所扮演的角色

          如前面一節(jié)所說(shuō),非線程安全的Gate類之所以會(huì)顯示BROKEN, 是因?yàn)閜ass方法內(nèi)的程序代碼可以被多個(gè)線程穿插執(zhí)行。
          synchronized 方法,能夠保證同時(shí)只有一個(gè)線程可以執(zhí)行它。這句話的意思是說(shuō):線程Alice執(zhí)行pass方法的時(shí)候,線程Bobby就不能調(diào)用pass方法。在線程 Alice執(zhí)行完pass方法之前,線程Bobby會(huì)在pass方法的入口處被阻擋下。當(dāng)線程Alice執(zhí)行完pass方法之后,將鎖定解除,線程 Bobby才可以開始執(zhí)行pass方法。

          Single Threaded Execution Pattern的所有參與者

          SharedResource(共享資源)參與者

          Single Threaded Execution Pattern中,有擔(dān)任SharedResource角色的類出現(xiàn)。在范例程序2中,Gate類就是這個(gè)SharedResource參與者。

          SharedResource參與者是可以由多個(gè)線程訪問(wèn)的類。SharedResource會(huì)擁有兩類方法:

          SafeMethod?? - 從多個(gè)線程同時(shí)調(diào)用也不會(huì)發(fā)生問(wèn)題的方法
          UnsafeMethod - 從多個(gè)線程同時(shí)調(diào)用會(huì)出問(wèn)題,而需要加以防護(hù)的方法。

          在Single Threaded Execution Pattern中,我們將UnsafeMethod加以防衛(wèi),限制同時(shí)只能有一個(gè)線程可以調(diào)用它。在Java語(yǔ)言中,只要將UnsafeMethod定義成synchronized方法,就可以實(shí)現(xiàn)這個(gè)目標(biāo)。

          這個(gè)必須只讓單線程執(zhí)行的程序范圍,被稱為臨界區(qū)(critical section)

          ???????????????????????????????????????????????? :SharedResource
          ---------?????????????????????????????????? -----------------
          :Thread? -----------------------|-> synchronized|
          ---------???????????????????????????????? ?|? UnsafeMethod1|
          ????????????????????????????????????????????? ?|????????????????????????? ?|
          ---------????????????????????????????????? |?????????????????????????? |
          :Thread? ---------------------->|?? synchronized|
          ---------?????????????????????????????????? |? UnsafeMethod2|
          ???????????????????????????????????????????????? -----------------

          擴(kuò)展思考方向的提示

          何時(shí)使用(適用性)

          多線程時(shí)

          單線程程序,并不需要使用Single Threaded Execution Pattern。因此,也不需要使用到synchronized方法。

          數(shù)據(jù)可以被多個(gè)線程訪問(wèn)的時(shí)候

          會(huì)需要使用Single Threaded Execution Pattern的情況,是在SharedResource的實(shí)例可能同時(shí)被多個(gè)線程訪問(wèn)的時(shí)候。
          就算是多線程程序,如果所有線程完全獨(dú)立運(yùn)行,那也沒(méi)有使用Single Threaded Execution Pattern的必要。我們將這個(gè)狀態(tài)稱為線程互不干涉(interfere)。
          有些管理多線程的環(huán)境,會(huì)幫我們確保線程的獨(dú)立性,這種情況下這個(gè)環(huán)境的用戶就不必考慮需不需要使用Single Thread Execution Pattern。

          狀態(tài)可能變化的時(shí)候

          當(dāng)SharedResource參與者狀態(tài)可能變化的時(shí)候,才會(huì)有使用Single Threaded Execution Pattern的需要。
          如果實(shí)例創(chuàng)建之后,從此不會(huì)改變狀態(tài),也沒(méi)有用用Single Threaded Execution Pattern的必要。

          第二章所要介紹的Immutable Pattern就是這種情況。在Immutable Pattern中,實(shí)例的狀態(tài)不會(huì)改變,所以是不需要用到synchronized方法的一種Pattern。

          需要確保安全性的時(shí)候

          只有需要確保安全性的時(shí)候,才會(huì)需要使用Single Threaded Execution Pattern。
          例如,Java的集合架構(gòu)類多半并非線程安全。這是為了在不考慮安全性的時(shí)候獲得更好的性能。
          所以用戶需要考慮自己要用的類需不需要考慮線程安全再使用。

          生命性與死鎖

          使用Single Threaded Execution Pattern時(shí),可能會(huì)發(fā)生死鎖(deadlock)的危險(xiǎn)。
          所謂死鎖,是指兩個(gè)線程分別獲取了鎖定,互相等待另一個(gè)線程解除鎖定的現(xiàn)象。發(fā)生死鎖的時(shí),兩個(gè)線程都無(wú)法繼續(xù)執(zhí)行下去,所以程序會(huì)失去生命性。

          舉個(gè)例子:

          假設(shè)Alice與Bobby同吃一個(gè)大盤子所盛放的意大利面。盤子的旁邊只有一支湯匙和一支叉子,而要吃意大利面時(shí),需要同時(shí)用到湯匙與叉子。

          只有一支的湯匙,被Alice拿去了,而只有一支的叉子,去被Bobby拿走了。就造成以下的情況:

          握著湯匙的Alice,一直等著Bobby把叉子放下。
          握著叉子的Bobby,一直等著Alice的湯匙放下。

          這么一來(lái)Alice和Bobby只有面面相覷,就這樣不動(dòng)了。像這樣,多個(gè)線程僵持不下,使程序無(wú)法繼續(xù)運(yùn)行的狀態(tài),就稱為死鎖。

          Single Threaded Execution達(dá)到下面這些條件時(shí),可能會(huì)出現(xiàn)死鎖的現(xiàn)象。

          1.具有多個(gè)SharedResource參與者
          2.線程鎖定一個(gè)SharedResource時(shí),還沒(méi)有解除鎖定就前去鎖定另一個(gè)SharedResource。
          3.線程獲取SharedResource參與者的順序不固定(和SharedResource參與者對(duì)等的)。

          回過(guò)頭來(lái)看前面吃不到意大利面的兩個(gè)人這個(gè)例子。
          1.多個(gè)SharedResource參與者,相當(dāng)于湯匙和叉子。
          2.鎖定某個(gè)SharedResource的參與者后,就去鎖定其他SharedResource。就相當(dāng)于握著湯匙而想要獲取對(duì)方的叉子,或握著叉子而想要獲取對(duì)方的湯匙這些操作。
          3.SharedResource角色是對(duì)等的,就像“拿湯匙->拿叉子”與“拿叉子->拿湯匙”兩個(gè)操作都可能發(fā)生。也就是說(shuō)在這里湯匙與叉子并沒(méi)有優(yōu)先級(jí)。

          1, 2, 3中只要破壞一個(gè)條件,就可以避免死鎖的發(fā)生。具體的程序代碼如問(wèn)題1-6

          ?

          問(wèn)題1-7

          某人正思考著若不使用synchronized,有沒(méi)有其他的方法可以做到Single Threaded Execution Pattern。而他寫下了如下的Gate類,如代碼1。那么接下來(lái)就是問(wèn)題。請(qǐng)創(chuàng)建Gate類中所要使用的Mutex類。

          順帶一提,像Mutex類這種用來(lái)進(jìn)行共享互斥的機(jī)制,一般稱為mutex。mutex是mutual exclusion(互斥)的簡(jiǎn)稱。

          代碼1

          ??? public class Gate {
          ??????? private int counter = 0;
          ??????? private String name = "Nobody";
          ??????? private String address = "Nowhere";
          ??????? private final Mutex mutex = new Mutex();
          ??????? public void pass(String name, String address) { // 并非synchronized
          ??????????? mutex.lock();
          ??????????? try {
          ??????????????? this.counter++;
          ??????????????? this.name = name;
          ??????????????? this.address = address;
          ??????????????? check();
          ??????????? } finally {
          ??????????????? mutex.unlock();
          ??????????? }
          ??????? }
          ??????? public String toString() { //? 并非synchronized
          ??????????? String s = null;
          ??????????? mutex.lock();
          ??????????? try {
          ??????????????? s = "No." + counter + ": " + name + ", " + address;
          ??????????? } finally {
          ??????????????? mutex.unlock();
          ??????????? }
          ??????????? return s;
          ??????? }
          ??????? private void check() {
          ??????????? if (name.charAt(0) != address.charAt(0)) {
          ??????????????? System.out.println("***** BROKEN ***** " + toString());
          ??????????? }
          ??????? }
          ??? }


          解答范例1:?jiǎn)渭兊腗utex類

          下面是最簡(jiǎn)單的 Mutex類,如代碼2。在此使用busy這個(gè)boolean類型的字段。busy若是true,就表示執(zhí)行了lock;如果busy是false,則表示執(zhí)行了unlock方法。lock與unlock雙方都已是synchronized方法保護(hù)著busy字段。

          代碼2

          ??? public final class Mutex {
          ??????? private boolean busy = false;
          ??????? public synchronized void lock() {
          ??????????? while (busy) {
          ??????????????? try {
          ??????????????????? wait();
          ??????????????? } catch (InterruptedException e) {
          ??????????????? }
          ??????????? }
          ??????????? busy = true;
          ??????? }
          ??????? public synchronized void unlock() {
          ??????????? busy = false;
          ??????????? notifyAll();
          ??????? }
          ??? }

          代碼2所示的Mutex類在問(wèn)題1-7會(huì)正確地執(zhí)行。但是,若使用于其他用途,則會(huì)發(fā)生如下問(wèn)題。這就是對(duì)使用Mutex類的限制。這意味著Mutex類的重復(fù)使用性上會(huì)有問(wèn)題。

          問(wèn)題點(diǎn)1
          假設(shè)有某個(gè)線程連續(xù)兩次調(diào)用lock方法。調(diào)用后,在第二次調(diào)用時(shí),由于busy字段已經(jīng)變成true,因此為wait。這就好像自己把自己鎖在外面,進(jìn)不了門的意思一樣。

          問(wèn)題點(diǎn)2
          即使是尚未調(diào)用出lock方法的線程,也會(huì)變成可以調(diào)用unlock方法。就好比即使不是自己上的鎖,自己還是可以將門打開一樣。

          解答范例2:改良后的Mutex類

          代碼3是將類似范例1中的問(wèn)題予以改良而形成的新的Mutex類。在此,現(xiàn)在的lock次數(shù)記錄在locks字段中。這個(gè)lock數(shù)是從在lock方法調(diào)用的次數(shù)扣掉在 unlock方法調(diào)用的次數(shù)得出的結(jié)果。連調(diào)用lock方法的線程也記錄在owner字段上。我們現(xiàn)在用locks和owner來(lái)解決上述的問(wèn)題點(diǎn)。

          代碼3

          ??? public final class Mutex {
          ??????? private long locks = 0;
          ??????? private Thread owner = null;
          ??????? public synchronized void lock() {
          ??????????? Thread me = Thread.currentThread();
          ??????????? while (locks > 0 && owner != me) {
          ??????????????? try {
          ??????????????????? wait();
          ??????????????? } catch (InterruptedException e) {
          ??????????????? }
          ??????????? }
          ??????????? //assert locks == 0 || owner == me
          ??????????? owner = me;
          ??????????? locks++;
          ??????? }
          ??????? public synchronized void unlock() {
          ??????????? Thread me = Thread.currentThread();
          ??????????? if (locks == 0 || owner != me) {
          ??????????????? return;
          ??????????? }
          ??????????? //assert locks > 0 && owner == me
          ??????????? locks--;
          ??????????? if (locks == 0) {
          ??????????????? owner = null;
          ??????????????? notifyAll();
          ??????????? }
          ??????? }
          ??? }


          測(cè)試代碼

          代碼4

          ??? public class UserThread extends Thread {
          ??????? private final Gate gate;
          ??????? private final String myname;
          ??????? private final String myaddress;
          ??????? public UserThread(Gate gate, String myname, String myaddress) {
          ??????????? this.gate = gate;
          ??????????? this.myname = myname;
          ??????????? this.myaddress = myaddress;
          ??????? }
          ??????? public void run() {
          ??????????? System.out.println(myname + " BEGIN");
          ??????????? while (true) {
          ??????????????? gate.pass(myname, myaddress);
          ??????????? }
          ??????? }
          ??? }

          代碼5

          ??? public class Main {
          ??????? public static void main(String[] args) {
          ??????????? System.out.println("Testing Gate, hit CTRL+C to exit.");
          ??????????? Gate gate = new Gate();
          ??????????? new UserThread(gate, "Alice", "Alaska").start();
          ??????????? new UserThread(gate, "Bobby", "Brazil").start();
          ??????????? new UserThread(gate, "Chris", "Canada").start();
          ??????? }
          ??? }
          posted on 2008-03-08 14:54 Eros 閱讀(273) 評(píng)論(0)  編輯  收藏

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 徐闻县| 岫岩| 裕民县| 昌平区| 克什克腾旗| 福鼎市| 勃利县| 长宁县| 贡嘎县| 麻江县| 平山县| 太仆寺旗| 福州市| 阿拉善左旗| 嘉祥县| 敦煌市| 色达县| 平塘县| 邵阳市| 嵊州市| 巩义市| 郎溪县| 威宁| 木里| 万宁市| 怀来县| 贵州省| 长春市| 红原县| 仪征市| 吉木萨尔县| 云浮市| 沿河| 扬中市| 全州县| 尼木县| 大厂| 乌审旗| 奉贤区| 清流县| 郴州市|