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

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

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

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

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

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

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

          表1-1 類一覽表
          --------------------------------------------------------------
          ?名稱??   ??????????????????? 說明
          --------------------------------------------------------------
          Main??????????  創(chuàng)建一個門,并操作3個人不斷地穿越門的類
          Gate?  ???????? 表示門的類,當人經(jīng)過時會記錄下姓名與出身地
          UserThread?????? 表示人的類,只負責處理不斷地在門間穿梭通過
          --------------------------------------------------------------

          Main類

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

          有下面3個人會通過這個門:

          Alice - Alaska
          Bobby - Brazil
          Chris - Canada

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

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

          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)表示人所要通過的門。

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

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

          this.name = name;

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

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

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

          這個Gate類,在單線程的時候可以正常運行,但是在多線程下就無法正常執(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)表示不斷穿越門的行人。這個類被聲明成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);
          ??????????? }
          ??????? }
          ??? }

          為什么會出錯呢?

          這是因為Gate類的pass方法會被多個線程調(diào)用的關(guān)系。pass方法是下面4行語句程序代碼所組成:

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

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

          圖1-3 線程Alice與線程Bobby調(diào)用pass方法時的執(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 ******
          -----------------------------------------------------------------------------------

          或者說交錯的情況如圖1-4所示,則調(diào)用check方法的時刻,name的值是"Bobby",而address的值會是"Alaska"。這個時候也會顯示出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é)果。
          通常,線程不會去考慮其他的線程,而自己只會一直不停地跑下去。“線程Alice現(xiàn)在執(zhí)行到的位置正指定name結(jié)束,還沒有指定address的值”,而線程Bobby對此情況并不知情。

          范例程序1之所以會顯示出BROKEN,是因為線程并沒有考慮到其他線程,而將共享實例的字段改寫了。
          對于name字段來說,有兩個線程在比賽,贏的一方先將值改寫。對address來說,也有兩個線程在比賽誰先將值改寫。像這樣子引發(fā)競爭(race)的狀況,我們稱為race condition。有race condition的情況時,就很難預(yù)測各字段的值了。


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

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

          線程安全的Gate類
          List 1-4 是線程安全的Gate類。需要修改的有兩個地方,在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é)所說,非線程安全的Gate類之所以會顯示BROKEN, 是因為pass方法內(nèi)的程序代碼可以被多個線程穿插執(zhí)行。
          synchronized 方法,能夠保證同時只有一個線程可以執(zhí)行它。這句話的意思是說:線程Alice執(zhí)行pass方法的時候,線程Bobby就不能調(diào)用pass方法。在線程 Alice執(zhí)行完pass方法之前,線程Bobby會在pass方法的入口處被阻擋下。當線程Alice執(zhí)行完pass方法之后,將鎖定解除,線程 Bobby才可以開始執(zhí)行pass方法。

          Single Threaded Execution Pattern的所有參與者

          SharedResource(共享資源)參與者

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

          SharedResource參與者是可以由多個線程訪問的類。SharedResource會擁有兩類方法:

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

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

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

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

          擴展思考方向的提示

          何時使用(適用性)

          多線程時

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

          數(shù)據(jù)可以被多個線程訪問的時候

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

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

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

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

          需要確保安全性的時候

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

          生命性與死鎖

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

          舉個例子:

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

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

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

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

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

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

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

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

          ?

          問題1-7

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

          順帶一提,像Mutex類這種用來進行共享互斥的機制,一般稱為mutex。mutex是mutual exclusion(互斥)的簡稱。

          代碼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:單純的Mutex類

          下面是最簡單的 Mutex類,如代碼2。在此使用busy這個boolean類型的字段。busy若是true,就表示執(zhí)行了lock;如果busy是false,則表示執(zhí)行了unlock方法。lock與unlock雙方都已是synchronized方法保護著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類在問題1-7會正確地執(zhí)行。但是,若使用于其他用途,則會發(fā)生如下問題。這就是對使用Mutex類的限制。這意味著Mutex類的重復(fù)使用性上會有問題。

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

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

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

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

          代碼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();
          ??????????? }
          ??????? }
          ??? }


          測試代碼

          代碼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 閱讀(271) 評論(0)  編輯  收藏

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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 东方市| 蓝田县| 乌苏市| 都江堰市| 汤阴县| 临城县| 定远县| 报价| 平乐县| 弋阳县| 麻城市| 宜州市| 罗甸县| 千阳县| 仁怀市| 宁南县| 米易县| 双柏县| 汝城县| 宜城市| 萍乡市| 大宁县| 中西区| 商洛市| 文昌市| 海伦市| 洛川县| 兴山县| 盐池县| 临朐县| 隆尧县| 英山县| 乐东| 全南县| 独山县| 同心县| 赞皇县| 泰宁县| 固镇县| 亳州市| 沅陵县|