<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] ***** # 23.1 基礎知識 ## 23.1.1 進程 **一個進程**就是一個執行中的程序,而每一個進程都有自己獨立的一塊內存空間、一組系統資源。在進程的概念中,每一個進程的內部數據和狀態都是完全獨立的。 在Windows操作系統下可以通過Ctrl+Alt+Del組合鍵查看進程,在UNIX和Linux操作系統下是通過ps命令查看進程的。打開Windows當前運行的進程,如圖23-1所示。 ![](https://box.kancloud.cn/40a27aef4506c63db7aacc6db1ea2d43_506x462.png) 在Windows操作系統中一個進程就是一個exe或者dll程序,它們相互獨立,互相也可以通信,在Android操作系統中進程間也可以通信。 ## 23.1.2 線程 一個進程中可以包含多個線程,線程是一段完成某個特定功能的代碼 ,是程序中單個順序控制的流程,同類的多個線程是共享一塊內存空間和一組系統資源。所以系統在各個線程之間切換時,開銷要比進程小的多,線程被稱為輕量級進程。 ## 23.1.3 主線程 Java程序至少會有一個線程,這就是主線程,程序啟動后是由JVM創建主線程,程序結束時由JVM停止主線程。主線程它負責管理子線程,即子線程的啟動、掛起、停止等等操作。圖23-2所示是進程、主線程和子線程的關系,其中主線程負責管理子線程,即子線程的啟動、掛起、停止等操作。 ![](https://box.kancloud.cn/973ff90bf460cb2fac34a10688f730bb_991x479.png) **圖23-2 進程、主線程和子線程關系** 獲取主線程示例代碼如下: ``` //HelloWorld.java文件 package lianl; public class HelloWorld { public static void main(String[] args) { //獲取主線程,在main方法中 Thread mainThread = Thread.currentThread(); System.out.println("主線程名:" + mainThread.getName()); } } ``` Java中創建一個子線程涉及到:java.lang.Thread類和java.lang.Runnable接口。 **Thread線程類:** 創建一個Thread對象就會產生一個新的線程。 **線程執行對象:** 實現Runnable接口的對象,線程執行的代碼是實現Runnable接口對象重寫run()方法中的代碼。 **提示**  **主線程中執行入口**是main(String\[\] args)方法,主線程可以控制程序的流程,管理其他的子線程。 **子線程執行入口**是線程執行對象的run()方法 # 23.2.1 實現Runnable接口 創建線程Thread對象時,可以將線程執行對象傳遞給它,這需要是使用Thread類如下兩個構造方法: * Thread(Runnable target, String name):target是線程執行對象,實現Runnable接口。name為線程指定一個名字。 * Thread(Runnable target):target是線程執行對象,實現Runnable接口。線程名字是由JVM分配的。 ***** **下面看一個具體示例,實現Runnable接口的線程執行對象Runner代碼如下:** ``` //Runner.java文件 package com.a51work6; //線程執行對象 public class Runner implements Runnable { ① // 編寫執行線程代碼 @Override public void run() { ② for (int i = 0; i < 10; i++) { // 打印次數和線程的名字 System.out.printf("第 %d次執行 - %s\n", i, Thread.currentThread().getName()); ③ try { // 隨機生成休眠時間 long sleepTime = (long) (1000 * Math.random()); // 線程休眠 Thread.sleep(sleepTime); ④ } catch (InterruptedException e) { } } // 線程執行結束 System.out.println("執行完成! " + Thread.currentThread().getName()); } } ``` 上述代碼第①行聲明實現Runnable接口,這要覆蓋代碼第②行的run()方法。run()方法是線程體,在該方法中編寫你自己的線程處理代碼。 本例中線程體中進行了十次循環,每次讓當前線程休眠一段時間。其中代碼第③行是打印次數和線程的名字,Thread.currentThread()可以獲得當前線程對象,getName()是Thread類的實例方法,可以獲得線程的名。代碼第④行Thread.sleep(sleepTime)是休眠當前線程, sleep是靜態方法它有兩個版本: * static void sleep(long millis):在指定的毫秒數內讓當前正在執行的線程休眠。 * static void sleep(long millis, int nanos) 在指定的毫秒數加指定的納秒數內讓當前正在執行的線程休眠。 **測試程序HelloWorld代碼如下:** ``` //Runner.java文件 package com.a51work6; //線程執行對象 public class Runner implements Runnable { ① // 編寫執行線程代碼 @Override public void run() { ② for (int i = 0; i < 10; i++) { // 打印次數和線程的名字 System.out.printf("第 %d次執行 - %s\n", i, Thread.currentThread().getName()); ③ try { // 隨機生成休眠時間 long sleepTime = (long) (1000 * Math.random()); // 線程休眠 Thread.sleep(sleepTime); ④ } catch (InterruptedException e) { } } // 線程執行結束 System.out.println("執行完成! " + Thread.currentThread().getName()); } } ``` 一臺PC通常就只有一顆CPU,在某個時刻只能是一個線程在運行,而Java語言在設計時就充分考慮到線程的并發調度執行。對于程序員來說,在編程時要注意給每個線程執行的時間和機會,主要是通過讓線程休眠的辦法(調用sleep()方法)來讓當前線程暫停執行,然后由其他線程來爭奪執行的機會。如果上面的程序中沒有用到sleep()方法,則就是第一個線程先執行完畢,然后第二個線程再執行完畢。所以用活sleep()方法是多線程編程的關鍵。 # 23.3 線程的狀態 * 新建狀態 新建狀態(New)是通過new等方式創建線程對象,它僅僅是一個空的線程對象。 * 就緒狀態 當主線程調用新建線程的start()方法后,它就進入就緒狀態(Runnable)。此時的線程尚未真正開始執行run()方法,它必須等待CPU的調度。 * 運行狀態 CPU的調度就緒狀態的線程,線程進入運行狀態(Running),處于運行狀態的線程獨占CPU,執行run()方法。 * 阻塞狀態 因為某種原因運行狀態的線程會進入不可運行狀態,即阻塞狀態(Blocked),處于阻塞狀態的線程JVM系統不能執行該線程,即使CPU空閑,也不能執行該線程。如下幾個原因會導致線程進入阻塞狀態: * 當前線程調用sleep()方法,進入休眠狀態。 * 被其他線程調用了join()方法,等待其他線程結束。 * 發出I/O請求,等待I/O操作完成期間。 * 當前線程調用wait()方法。 處于阻塞狀態可以重新回到就緒狀態,如:休眠結束、其他線程加入、I/O操作完成和調用notify或notifyAll喚醒wait線程。 * 死亡狀態 線程退出run()方法后,就會進入死亡狀態(Dead),線程進入死亡狀態有可以是正常實現完成run()方法進入,也可能是由于發生異常而進入的。 ![](https://box.kancloud.cn/2bfa11ca505f381bfb0c52d8712bf781_1020x298.png) # 23.4 線程管理 ## 23.4.1 線程優先級 線程的調度程序根據線程決定每次線程應當何時運行,Java提供了10種優先級,分別用1~10整數表示,最高優先級是10用常量MAX\_PRIORITY表示;最低優先級是1用常量MIN\_PRIORITY;默認優先級是5用常量NORM\_PRIORITY表示。 Thread類提供了setPriority(int newPriority)方法可以設置線程優先級,通過getPriority()方法獲得線程優先級。 **代碼:** ![](https://box.kancloud.cn/893525eaf954020c0c71c5601c9c46e8_825x487.png) ## 23.4.2 等待線程結束 在介紹現在狀態時提到過join()方法,當前主線程調用t1線程的join()方法,則阻塞當前主線程,等待t1線程,如果t1線程結束或等待超時,則當前線程回到就緒狀態。 Thread類提供了多個版本的join(),它們定義如下: * void join():等待該線程結束。 * void join(long millis):等待該線程結束的時間最長為millis毫秒。如果超時為0意味著要一直等下去。 * void join(long millis, int nanos):等待該線程結束的時間最長為millis毫秒加nanos納秒。 使用join()方法示例代碼如下: ~~~ //HelloWorld.java文件 package com.a51work6; public class HelloWorld { //共享變量 static int value = 0; ① public static void main(String[] args) throws InterruptedException { System.out.println("主線程 開始..."); // 創建線程t1,參數是一個線程執行對象Runner Thread t1 = new Thread(() -> { ② System.out.println("ThreadA 開始..."); for (int i = 0; i < 2; i++) { System.out.println("ThreadA 執行..."); value++; ③ } System.out.println("ThreadA 結束..."); }, "ThreadA"); // 開始線程t1 t1.start(); // 主線程被阻塞,等待t1線程結束 t1.join(); ④ System.out.println("value = " + value); ⑤ System.out.println("主線程 結束..."); } } ~~~ 運行結果如下: ~~~ 主線程 開始... ThreadA 開始... ThreadA 執行... ThreadA 執行... ThreadA 結束... value = 2 主線程 結束... ~~~ 上述代碼第①行是聲明了一個共享變量value,這個變量在子線程中修改,然后主線程訪問它。代碼第②行是采用Lambda表達式創建線程,指定線程名為ThreadA。代碼第③行是在子線程ThreadA中修改共享變量value。 代碼第④行是在當前線程(主線程)中調用t1的join()方法,因此會導致主線程阻塞,等待t1線程結束。t1結束后,再執行主線程。代碼第⑤行是打印共享變量value,從運行結果可見value = 2。 如果嘗試將t1.join()語句注釋掉,輸出結果如下,因為此時兩個線程交替運: ~~~ 主線程 開始... value = 0 主線程 結束... ThreadA 開始... ThreadA 執行... ThreadA 執行... ThreadA 結束... ~~~ > **提示** 使用join()方法的場景是,一個線程依賴于另外一個線程的運行結果,所以調用另一個線程的join()方法等它運行完成。 ### 23.4.3 線程讓步 線程類Thread提供靜態方法yield(),調用yield()方法能夠使當前線程給其他線程讓步。它類似于sleep()方法,能夠使運行狀態的線程放棄CPU使用權,暫停片刻,然后重新回到就緒狀態。 與sleep()方法不同的是,sleep()方法是線程進行休眠,能夠給其他線程運行的機會,無論線程優先級高低都有機會運行。而yield()方法只給相同優先級或更高優先級線程機會。 示例代碼如下: ~~~ //Runner.java文件 package com.a51work6; //線程執行對象 public class Runner implements Runnable { // 編寫執行線程代碼 @Override public void run() { for (int i = 0; i < 10; i++) { // 打印次數和線程的名字 System.out.printf("第 %d次執行 - %s\n", i, Thread.currentThread().getName()); Thread.yield(); ① } // 線程執行結束 System.out.println("執行完成! " + Thread.currentThread().getName()); } } ~~~ 代碼第①行Thread.yield()能夠使當前線程讓步。 > **提示** yield()方法只能給相同優先級或更高優先級的線程讓步,yield()方法在實際開發中很少使用, > 因為不可以控制時間,而sleep()方法可以。 ### 23.4.4 線程停止 線程體中的run()方法結束,線程進入死亡狀態,線程就停止了。但是有些業務比較復雜,例如想開發一個下載程序,每隔一段執行一次下載任務,下載任務一般會在由子線程執行的,休眠一段時間再執行。這個下載子線程中會有一個死循環,但是為了能夠停止子線程,設置一個結束變量。 示例下面如下: ~~~ //HelloWorld.java文件 package com.a51work6; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class HelloWorld { private static String command = ""; ① public static void main(String[] args) { // 創建線程t1,參數是一個線程執行對象Runner Thread t1 = new Thread(() -> { // 一直循環,直到滿足條件在停止線程 while (!command.equalsIgnoreCase("exit")) { ② // 線程開始工作 // TODO System.out.println("下載中..."); try { // 線程休眠 Thread.sleep(10000); } catch (InterruptedException e) { } } // 線程執行結束 System.out.println("執行完成!"); }); // 開始線程t1 t1.start(); try (InputStreamReader ir = new InputStreamReader(System.in); ③ BufferedReader in = new BufferedReader(ir)) { // 從鍵盤接收了一個字符串的輸入 command = in.readLine(); ④ } catch (IOException e) { } } } ~~~ 上述代碼第①行是設置一個結束變量。代碼第②行是在子線程的線程體中判斷,用戶輸入的是否為exit字符串,如果不是則進行循環,否則結束循環,結束循環就結束了run()方法,線程就停止了。 代碼第③行中的System.in是一個很特殊的輸入流,能夠從控制臺(鍵盤)讀取字符。代碼第④行是通過流System.in讀取鍵盤輸入的字符串。測試是需要注意:在控制臺輸入exit,然后敲Enter鍵,如圖23-6所示。 ***** ## 23.5 線程安全 在多線程環境下,訪問相同的資源,有可以會引發線程不安全問題。本節討論引發這些問題的根源和解決方法。 ### 23.5.1 臨界資源問題 多一個線程同時運行,有時線程之間需要共享數據,一個線程需要其他線程的數據,否則就不能保證程序運行結果的正確性。 例如有一個航空公司的機票銷售,每一天機票數量是有限的,很多售票點同時銷售這些機票。下面是一個模擬銷售機票系統,示例代碼如下: ~~~ //TicketDB.java文件 package com.a51work6; //機票數據庫 public class TicketDB { // 機票的數量 private int ticketCount = 5; ① // 獲得當前機票數量 public int getTicketCount() { ② return ticketCount; } // 銷售機票 public void sellTicket() { ③ try { // 等于用戶付款 // 線程休眠,阻塞當前線程,模擬等待用戶付款 Thread.sleep(1000); ④ } catch (InterruptedException e) { } System.out.printf("第%d號票,已經售出\n", ticketCount); ticketCount--; ⑤ } } ~~~ 上述代碼模擬機票銷售過程,代碼第①行是聲明機票數量成員變量ticketCount,這是模擬當天可供銷售的機票數,為了測試方便初始值設置為5。代碼第②行是定義了獲取當前機票數的getTicketCount()方法。代碼第③行是銷售機票方法,售票網點查詢出有沒有票可以銷售,那么會調用sellTicket()方法銷售機票,這個過程中需要等待用戶付款,付款成功后,會將機票數減一,見代碼第⑤行。為模擬等待用戶付款,在代碼第④行使用了sleep()方法讓當前線程阻塞。 調用代碼如下: ~~~ //HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String[] args) { TicketDB db = new TicketDB(); // 創建線程t1 Thread t1 = new Thread(() -> { while (true) { int currTicketCount = db.getTicketCount(); ① // 查詢是否有票 if (currTicketCount > 0) { ② db.sellTicket(); ③ } else { // 無票退出 break; } } }); // 開始線程t1 t1.start(); // 創建線程t2 Thread t2 = new Thread(() -> { while (true) { int currTicketCount = db.getTicketCount(); // 查詢是否有票 if (currTicketCount > 0) { db.sellTicket(); } else { // 無票退出 break; } } }); // 開始線程t2 t2.start(); } } ~~~ 在HelloWorld中創建了兩個線程,模擬兩個售票網點,沒有線程所做的事情類似。首先獲得當前機票數量(見代碼第①行),然后判斷機票數量是否大于零(見代碼第②行),如果有票則出票(見代碼第②行),否則退出循環,結束線程。 一次運行結果如下: ~~~ 第5號票,已經售出 第5號票,已經售出 第3號票,已經售出 第3號票,已經售出 第1號票,已經售出 第0號票,已經售出 ~~~ 雖然可以能每次運行的結果都不一樣,但是從結果看還是能發現一些問題:同一張票重復銷售了兩次。這些問題的原因是多個線程間共享的數據導致數據的不一致性。 > **提示** 多個線程間共享的數據稱為共享資源或臨界資源,由于是CPU負責線程的調度,程序員無法精確控制多線程的交替順序。這種情況下,多線程對臨界資源的訪問有時會導致數據的不一致性。 ## 23.5 線程安全 在多線程環境下,訪問相同的資源,有可以會引發線程不安全問題。 ### 23.5.1 臨界資源問題 多個線程同時運行,有時線程之間需要共享數據,一個線程需要其他線程的數據,否則就不能保證程序運行結果的正確性。 例如有一個航空公司的機票銷售,每一天機票數量是有限的,很多售票點同時銷售這些機票。下面是一個模擬銷售機票系統,示例代碼如下: ~~~ //TicketDB.java文件 package com.a51work6; //機票數據庫 public class TicketDB { // 機票的數量 private int ticketCount = 5; ① // 獲得當前機票數量 public int getTicketCount() { ② return ticketCount; } // 銷售機票 public void sellTicket() { ③ try { // 等于用戶付款 // 線程休眠,阻塞當前線程,模擬等待用戶付款 Thread.sleep(1000); ④ } catch (InterruptedException e) { } System.out.printf("第%d號票,已經售出\n", ticketCount); ticketCount--; ⑤ } } ~~~ 上述代碼模擬機票銷售過程,代碼第①行是聲明機票數量成員變量ticketCount,這是模擬當天可供銷售的機票數,為了測試方便初始值設置為5。代碼第②行是定義了獲取當前機票數的getTicketCount()方法。代碼第③行是銷售機票方法,售票網點查詢出有沒有票可以銷售,那么會調用sellTicket()方法銷售機票,這個過程中需要等待用戶付款,付款成功后,會將機票數減一,見代碼第⑤行。為模擬等待用戶付款,在代碼第④行使用了sleep()方法讓當前線程阻塞。 調用代碼如下: ~~~ //HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String[] args) { TicketDB db = new TicketDB(); // 創建線程t1 Thread t1 = new Thread(() -> { while (true) { int currTicketCount = db.getTicketCount(); ① // 查詢是否有票 if (currTicketCount > 0) { ② db.sellTicket(); ③ } else { // 無票退出 break; } } }); // 開始線程t1 t1.start(); // 創建線程t2 Thread t2 = new Thread(() -> { while (true) { int currTicketCount = db.getTicketCount(); // 查詢是否有票 if (currTicketCount > 0) { db.sellTicket(); } else { // 無票退出 break; } } }); // 開始線程t2 t2.start(); } } ~~~ 在HelloWorld中創建了兩個線程,模擬兩個售票網點,沒有線程所做的事情類似。首先獲得當前機票數量(見代碼第①行),然后判斷機票數量是否大于零(見代碼第②行),如果有票則出票(見代碼第②行),否則退出循環,結束線程。 一次運行結果如下: ~~~ 第5號票,已經售出 第5號票,已經售出 第3號票,已經售出 第3號票,已經售出 第1號票,已經售出 第0號票,已經售出 ~~~ 雖然可以能每次運行的結果都不一樣,但是從結果看還是能發現一些問題:同一張票重復銷售了兩次。這些問題的根本原因是多個線程間共享的數據導致數據的不一致性。 > **提示** 多個線程間共享的數據稱為共享資源或臨界資源,由于是CPU負責線程的調度,程序員無法精確控制多線程的交替順序。這種情況下,多線程對臨界資源的訪問有時會導致數據的不一致性。 ***** ### 23.5.2 多線程同步 為了防止多線程對臨界資源的訪問有時會導致數據的不一致性,Java提供了“互斥”機制,可以**為這些資源對象加上一把“互斥鎖”**,在任一時刻只能由一個線程訪問。 即使該線程出現阻塞,該對象的被鎖定狀態也不會解除,其他線程仍不能訪問該對象,這就多線程同步。 線程同步保證線程安全的重要手段,但是線程同步客觀上會導致性能下降。 **兩種方式實現線程同步** * 一種是synchronized方法,使用synchronized關鍵字修飾方法,對方法進行同步 * 另一種是synchronized語句,使用synchronized關鍵字放在對象前面限制一段代碼的執行。 **1\. synchronized方法** synchronized關鍵字修飾方法實現線程同步,方法所在的對象被鎖定,修改23.5.1節售票系統示例, TicketDB.java文件代碼如下: ``` ~~~ //TicketDB.java文件 package com.a51work6.method;   //機票數據庫 public class TicketDB {   // 機票的數量 private int ticketCount = 5;   // 獲得當前機票數量 public synchronized int getTicketCount() { ① return ticketCount; }   // 銷售機票 public synchronized void sellTicket() { ② try { // 等于用戶付款 // 線程休眠,阻塞當前線程,模擬等待用戶付款 Thread.sleep(1000); } catch (InterruptedException e) { } System.out.printf("第%d號票,已經售出\n", ticketCount); ticketCount--; } } ~~~ 上述代碼第①行和第②行的方法前都使用了synchronized關鍵字,表明這兩個方法是同步的,被鎖定的,每一個時刻只能由一個線程訪問。并不是每一個方法都有必要加鎖的,要仔細研究加上的必要性,上述代碼第①行加鎖可以防止出現第0號票情況和5張票賣出2次的情況;代碼第②行加鎖是防止出現銷售兩種一樣的票,讀者可以自己測試一下。 采用synchronized方法修改示例,調用代碼HelloWorld.java不需要任何修改。 ``` **2. synchronized語句** synchronized語句方式主要用于第三方類,不方便修改它的代碼情況。同樣是23.5.1節售票系統示例,可以不用修改TicketDB.java類,只修改調用代碼HelloWorld.java實現同步。 ``` HelloWorld.java代碼如下: //HelloWorld.java文件 package com.a51work6.statement;   public class HelloWorld {   public static void main(String[] args) {   TicketDB db = new TicketDB();   // 創建線程t1 Thread t1 = new Thread(() -> { while (true) { synchronized (db) { ① int currTicketCount = db.getTicketCount(); // 查詢是否有票 if (currTicketCount > 0) { db.sellTicket(); } else { // 無票退出 break; } } } }); // 開始線程t1 t1.start();   // 創建線程t2 Thread t2 = new Thread(() -> { while (true) { synchronized (db) { ② int currTicketCount = db.getTicketCount(); // 查詢是否有票 if (currTicketCount > 0) { db.sellTicket(); } else { // 無票退出 break; } } } }); // 開始線程t2 t2.start(); } } ``` 代碼第①行和第②行是使用synchronized語句,將需要同步的代碼用大括號括起來。synchronized后有小括號,將需要同步的對象括起來。 ***** ## 23.6 線程間通信 第23.5節的示例只是簡單地為特定對象或方法加鎖,但有時情況會更加復雜,如果兩個線程之間有依賴關系,線程之間必須進行通信,互相協調才能完成工作。 例如有一個經典的堆棧問題,一個線程生成了一些數據,將數據壓棧;另一個線程消費了這些數據,將數據出棧。這兩個線程互相依賴,當堆棧為空時,消費線程無法取出數據時,應該通知生成線程添加數據;當堆棧已滿時,生產線程無法添加數據時,應該通知消費線程取出數據。 為了實現線程間通信,需要使用Object類中聲明的5個方法: * void wait():使當前線程釋放對象鎖,然后當前線程處于對象等待隊列中阻塞狀態,如圖23-7所示,等待其他線程喚醒。 * void wait(long timeout):同wait()方法,等待timeout毫秒時間。 * void wait(long timeout, int nanos):同wait()方法,等待timeout毫秒加nanos納秒時間。 * void notify():當前線程喚醒此對象等待隊列中的一個線程,如圖23-7所示該線程將進入就緒狀態。 * void notifyAll():當前線程喚醒此對象等待隊列中的所有線程,如圖23-7所示這些線程將進入就緒狀態。 ![](http://www.ituring.com.cn/figures/2017/Javarookietomaster/24.d23z.007.png) **圖23-7 線程間通信** > **提示** 圖23-7是圖23-5補充,從圖23-7可見,線程有多種方式進入阻塞狀態,除了通過wait()外還有,加鎖的方式和其他方式,加鎖方式是23.5節介紹的使用synchronized加互斥鎖;其他方式事實上是23.3節線程狀態時介紹的方式,這里不再贅述。 下面看看消費和生產示例中堆棧類代碼: ~~~ //Stack.java文件 package com.a51work6; //堆棧類 class Stack { // 堆棧指針初始值為0 private int pointer = 0; // 堆棧有5個字符的空間 private char[] data = new char[5]; // 壓棧方法,加上互斥鎖 public synchronized void push(char c) { ① // 堆棧已滿,不能壓棧 while (pointer == data.length) { try { // 等待,直到有數據出棧 this.wait(); } catch (InterruptedException e) { } } // 通知其他線程把數據出棧 this.notify(); // 數據壓棧 data[pointer] = c; // 指針向上移動 pointer++; } // 出棧方法,加上互斥鎖 public synchronized char pop() { ② // 堆棧無數據,不能出棧 while (pointer == 0) { try { // 等待其他線程把數據壓棧 this.wait(); } catch (InterruptedException e) { } } // 通知其他線程壓棧 this.notify(); // 指針向下移動 pointer--; // 數據出棧 return data[pointer]; } } ~~~ 上述代碼實現了同步堆棧類,該堆棧有最多5個元素的空間,代碼第①行聲明了壓棧方法push(),該方法是一個同步方法,在該方法中首先判斷是否堆棧已滿,如果已滿不能壓棧,調用this.wait()讓當前線程進入對象等待狀態中。如果堆棧未滿,程序會往下運行調用this.notify()喚醒對象等待隊列中的一個線程。代碼第②行聲明了出棧方法pop()方法,與push()方法類似,這里不再贅述。 調用代碼如下: ``` //HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String args[]) { Stack stack = new Stack(); ① // 下面的消費者和生產者所操作的是同一個堆棧對象stack // 生產者線程 Thread producer = new Thread(() -> { ② char c; for (int i = 0; i < 10; i++) { // 隨機產生10個字符 c = (char) (Math.random() * 26 + 'A'); // 把字符壓棧 stack.push(c); // 打印字符 System.out.println("生產: " + c); try { // 每產生一個字符線程就睡眠 Thread.sleep((int) (Math.random() * 1000)); } catch (InterruptedException e) { } } }); // 消費者線程 Thread consumer = new Thread(() -> { ③ char c; for (int i = 0; i < 10; i++) { // 從堆棧中讀取字符 c = stack.pop(); // 打印字符 System.out.println("消費: " + c); try { // 每讀取一個字符線程就睡眠 Thread.sleep((int) (Math.random() * 1000)); } catch (InterruptedException e) { } } }); producer.start(); // 啟動生產者線程 consumer.start(); // 啟動消費者線程 } } ``` 上述代碼第①行創建堆棧對象。代碼第②行創建生產者線程,代碼第③行創建消費者線程。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看