免费爱碰视频在线观看,九九精品国产屋,欧美亚洲尤物久久精品,1024在线观看视频亚洲

      一文參透并發(fā)編程,阿里大牛兩萬字總結(jié) +40 張圖文詳解

      一文參透并發(fā)編程,阿里大牛兩萬字總結(jié) +40 張圖文詳解

      分布式模型的思想就是借鑒并發(fā)模型的基礎(chǔ)上推演發(fā)展來的。

      認識兩個狀態(tài)

      并發(fā)模型的一個重要的方面是,線程是否應該共享狀態(tài),是具有共享狀態(tài)還是獨立狀態(tài)。共享狀態(tài)也就意味著在不同線程之間共享某些狀態(tài)

      狀態(tài)其實就是數(shù)據(jù),比如一個或者多個對象。當線程要共享數(shù)據(jù)時,就會造成競態(tài)條件或者死鎖等問題。當然,這些問題只是可能會出現(xiàn),具體實現(xiàn)方式取決于你是否安全的使用和訪問共享對象。

      獨立的狀態(tài)表明狀態(tài)不會在多個線程之間共享,如果線程之間需要通信的話,他們可以訪問不可變的對象來實現(xiàn),這是最有效的避免并發(fā)問題的一種方式,如下圖所示

      使用獨立狀態(tài)讓我們的設(shè)計更加簡單,因為只有一個線程能夠訪問對象,即使交換對象,也是不可變的對象。

      并發(fā)模型

      并行 Worker

      第一個并發(fā)模型是并行 worker 模型,客戶端會把任務交給代理人(Delegator),然后由代理人把工作分配給不同的工人(worker)。如下圖所示

      并行 worker 的核心思想是,它主要

      有兩個進程即代理人和工人,Delegator 負責接收來自客戶端的任務并把任務下發(fā),交給具體的 Worker 進行處理,Worker 處理完成后把結(jié)果返回給 Delegator,在 Delegator 接收到 Worker 處理的結(jié)果后對其進行匯總,然后交給客戶端。

      并行 Worker 模型是 Java 并發(fā)模型中非常常見的一種模型。許多 java.util.concurrent 包下的并發(fā)工具都使用了這種模型。

      并行 Worker 的優(yōu)點

      并行 Worker 模型的一個非常明顯的特點就是很容易理解,為了提高系統(tǒng)的并行度你可以增加多個 Worker 完成任務。

      并行 Worker 模型的另外一個好處就是,它會將一個任務拆分成多個小任務,并發(fā)執(zhí)行,Delegator 在接受到 Worker 的處理結(jié)果后就會返回給 Client,整個 Worker -> Delegator -> Client 的過程是異步的。

      并行 Worker 的缺點

      同樣的,并行 Worker 模式同樣會有一些隱藏的缺點

      共享狀態(tài)會變得很復雜

      實際的并行 Worker 要比我們圖中畫出的更復雜,主要是并行 Worker 通常會訪問內(nèi)存或共享數(shù)據(jù)庫中的某些共享數(shù)據(jù)。

      這些共享狀態(tài)可能會使用一些工作隊列來保存業(yè)務數(shù)據(jù)、數(shù)據(jù)緩存、數(shù)據(jù)庫的連接池等。在線程通信中,線程需要確保共享狀態(tài)是否能夠讓其他線程共享,而不是僅僅停留在 CPU 緩存中讓自己可用,當然這些都是程序員在設(shè)計時就需要考慮的問題。線程需要避免競態(tài)條件,死鎖和許多其他共享狀態(tài)造成的并發(fā)問題。

      多線程在訪問共享數(shù)據(jù)時,會丟失并發(fā)性,因為操作系統(tǒng)要保證只有一個線程能夠訪問數(shù)據(jù),這會導致共享數(shù)據(jù)的爭用和搶占。未搶占到資源的線程會阻塞。

      現(xiàn)代的非阻塞并發(fā)算法可以減少爭用提高性能,但是非阻塞算法比較難以實現(xiàn)。

      可持久化的數(shù)據(jù)結(jié)構(gòu)(Persistent data structures)是另外一個選擇??沙志没臄?shù)據(jù)結(jié)構(gòu)在修改后始終會保留先前版本。因此,如果多個線程同時修改一個可持久化的數(shù)據(jù)結(jié)構(gòu),并且一個線程對其進行了修改,則修改的線程會獲得對新數(shù)據(jù)結(jié)構(gòu)的引用。

      雖然可持久化的數(shù)據(jù)結(jié)構(gòu)是一個新的解決方法,但是這種方法實行起來卻有一些問題,比如,一個持久列表會將新元素添加到列表的開頭,并返回所添加的新元素的引用,但是其他線程仍然只持有列表中先前的第一個元素的引用,他們看不到新添加的元素。

      持久化的數(shù)據(jù)結(jié)構(gòu)比如鏈表(LinkedList)在硬件性能上表現(xiàn)不佳。列表中的每個元素都是一個對象,這些對象散布在計算機內(nèi)存中?,F(xiàn)代 CPU 的順序訪問往往要快的多,因此使用數(shù)組等順序訪問的數(shù)據(jù)結(jié)構(gòu)則能夠獲得更高的性能。CPU 高速緩存可以將一個大的矩陣塊加載到高速緩存中,并讓 CPU 在加載后直接訪問 CPU 高速緩存中的數(shù)據(jù)。對于鏈表,將元素分散在整個 RAM 上,這實際上是不可能的。

      無狀態(tài)的 worker

      共享狀態(tài)可以由其他線程所修改,因此,worker 必須在每次操作共享狀態(tài)時重新讀取,以確保在副本上能夠正確工作。不在線程內(nèi)部保持狀態(tài)的 worker 成為無狀態(tài)的 worker。

      作業(yè)順序是不確定的

      并行工作模型的另一個缺點是作業(yè)的順序不確定,無法保證首先執(zhí)行或最后執(zhí)行哪些作業(yè)。任務 A 在任務 B 之前分配給 worker,但是任務 B 可能在任務 A 之前執(zhí)行。

      流水線

      第二種并發(fā)模型就是我們經(jīng)常在生產(chǎn)車間遇到的流水線并發(fā)模型,下面是流水線設(shè)計模型的流程圖

      這種組織架構(gòu)就像是工廠中裝配線中的 worker,每個 worker 只完成全部工作的一部分,完成一部分后,worker 會將工作轉(zhuǎn)發(fā)給下一個 worker。

      每道程序都在自己的線程中運行,彼此之間不會共享狀態(tài),這種模型也被稱為無共享并發(fā)模型。

      使用流水線并發(fā)模型通常被設(shè)計為非阻塞 I/O,也就是說,當沒有給 worker 分配任務時,worker 會做其他工作。非阻塞 I/O 意味著當 worker 開始 I/O 操作,例如從網(wǎng)絡中讀取文件,worker 不會等待 I/O 調(diào)用完成。因為 I/O 操作很慢,所以等待 I/O 非常耗費時間。在等待 I/O 的同時,CPU 可以做其他事情,I/O 操作完成后的結(jié)果將傳遞給下一個 worker。下面是非阻塞 I/O 的流程圖

      在實際情況中,任務通常不會按著一條裝配線流動,由于大多數(shù)程序需要做很多事情,因此需要根據(jù)完成的不同工作在不同的 worker 之間流動,如下圖所示

      任務還可能需要多個 worker 共同參與完成

      響應式 – 事件驅(qū)動系統(tǒng)

      使用流水線模型的系統(tǒng)有時也被稱為響應式或者事件驅(qū)動系統(tǒng),這種模型會根據(jù)外部的事件作出響應,事件可能是某個 HTTP 請求或者某個文件完成加載到內(nèi)存中。

      Actor 模型

      在 Actor 模型中,每一個 Actor 其實就是一個 Worker, 每一個 Actor 都能夠處理任務。

      簡單來說,Actor 模型是一個并發(fā)模型,它定義了一系列系統(tǒng)組件應該如何動作和交互的通用規(guī)則,最著名的使用這套規(guī)則的編程語言是 Erlang。一個參與者 Actor 對接收到的消息做出響應,然后可以創(chuàng)建出更多的 Actor 或發(fā)送更多的消息,同時準備接收下一條消息。

      Channels 模型

      在 Channel 模型中,worker 通常不會直接通信,與此相對的,他們通常將事件發(fā)送到不同的通道(Channel)上,然后其他 worker 可以在這些通道上獲取消息,下面是 Channel 的模型圖

      有的時候 worker 不需要明確知道接下來的 worker 是誰,他們只需要將作者寫入通道中,監(jiān)聽 Channel 的 worker 可以訂閱或者取消訂閱,這種方式降低了 worker 和 worker 之間的耦合性。

      流水線設(shè)計的優(yōu)點

      與并行設(shè)計模型相比,流水線模型具有一些優(yōu)勢,具體優(yōu)勢如下

      不會存在共享狀態(tài)

      因為流水線設(shè)計能夠保證 worker 在處理完成后再傳遞給下一個 worker,所以 worker 與 worker 之間不需要共享任何狀態(tài),也就無需考慮并發(fā)問題。你甚至可以在實現(xiàn)上把每個 worker 看成是單線程的一種。

      有狀態(tài) worker

      因為 worker 知道沒有其他線程修改自身的數(shù)據(jù),所以流水線設(shè)計中的 worker 是有狀態(tài)的,有狀態(tài)的意思是他們可以將需要操作的數(shù)據(jù)保留在內(nèi)存中,有狀態(tài)通常比無狀態(tài)更快。

      更好的硬件整合

      因為你可以把流水線看成是單線程的,而單線程的工作優(yōu)勢在于它能夠和硬件的工作方式相同。因為有狀態(tài)的 worker 通常在 CPU 中緩存數(shù)據(jù),這樣可以更快地訪問緩存的數(shù)據(jù)。

      使任務更加有效的進行

      可以對流水線并發(fā)模型中的任務進行排序,一般用來日志的寫入和恢復。

      流水線設(shè)計的缺點

      流水線并發(fā)模型的缺點是任務會涉及多個 worker,因此可能會分散在項目代碼的多個類中。因此很難確定每個 worker 都在執(zhí)行哪個任務。流水線的代碼編寫也比較困難,設(shè)計許多嵌套回調(diào)處理程序的代碼通常被稱為回調(diào)地獄。回調(diào)地獄很難追蹤 debug。

      函數(shù)性并行

      函數(shù)性并行模型是最近才提出的一種并發(fā)模型,它的基本思路是使用函數(shù)調(diào)用來實現(xiàn)。消息的傳遞就相當于是函數(shù)的調(diào)用。傳遞給函數(shù)的參數(shù)都會被拷貝,因此在函數(shù)之外的任何實體都無法操縱函數(shù)內(nèi)的數(shù)據(jù)。這使得函數(shù)執(zhí)行類似于原子操作。每個函數(shù)調(diào)用都可以獨立于任何其他函數(shù)調(diào)用執(zhí)行。

      當每個函數(shù)調(diào)用獨立執(zhí)行時,每個函數(shù)都可以在單獨的 CPU 上執(zhí)行。這也就是說,函數(shù)式并行并行相當于是各個 CPU 單獨執(zhí)行各自的任務。

      JDK 1.7 中的 ForkAndJoinPool 類就實現(xiàn)了函數(shù)性并行的功能。Java 8 提出了 stream 的概念,使用并行流也能夠?qū)崿F(xiàn)大量集合的迭代。

      函數(shù)性并行的難點是要知道函數(shù)的調(diào)用流程以及哪些 CPU 執(zhí)行了哪些函數(shù),跨 CPU 函數(shù)調(diào)用會帶來額外的開銷。

      我們之前說過,線程就是進程中的一條順序流,在 Java 中,每一條 Java 線程就像是 JVM 的一條順序流,就像是虛擬 CPU 一樣來執(zhí)行代碼。Java 中的 main()方法是一條特殊的線程,JVM 創(chuàng)建的 main 線程是一條主執(zhí)行線程,在 Java 中,方法都是由 main 方法發(fā)起的。在 main 方法中,你照樣可以創(chuàng)建其他的線程(執(zhí)行順序流),這些線程可以和 main 方法共同執(zhí)行應用代碼。

      Java 線程也是一種對象,它和其他對象一樣。Java 中的 Thread 表示線程,Thread 是 java.lang.Thread 類或其子類的實例。那么下面我們就來一起探討一下在 Java 中如何創(chuàng)建和啟動線程。

      創(chuàng)建并啟動線程

      在 Java 中,創(chuàng)建線程的方式主要有三種

      • 通過繼承 Thread 類來創(chuàng)建線程
      • 通過實現(xiàn) Runnable 接口來創(chuàng)建線程
      • 通過 Callable 和 Future 來創(chuàng)建線程

      下面我們分別探討一下這幾種創(chuàng)建方式

      繼承 Thread 類來創(chuàng)建線程

      第一種方式是繼承 Thread 類來創(chuàng)建線程,如下示例

      線程的主要創(chuàng)建步驟如下

      • 定義一個線程類使其繼承 Thread 類,并重寫其中的 run 方法,run 方法內(nèi)部就是線程要完成的任務,因此 run 方法也被稱為執(zhí)行體
      • 創(chuàng)建了 Thread 的子類,上面代碼中的子類是 TJavaThread
      • 啟動方法需要注意,并不是直接調(diào)用 run 方法來啟動線程,而是使用 start 方法來啟動線程。當然 run 方法可以調(diào)用,這樣的話就會變成普通方法調(diào)用,而不是新創(chuàng)建一個線程來調(diào)用了。

      這樣的話,整個 main 方法只有一條執(zhí)行線程也就是 main 線程,由兩條執(zhí)行線程變?yōu)橐粭l執(zhí)行線程

      Thread 構(gòu)造器只需要一個 Runnable 對象,調(diào)用 Thread 對象的 start() 方法為該線程執(zhí)行必須的初始化操作,然后調(diào)用 Runnable 的 run 方法,以便在這個線程中啟動任務。我們上面使用了線程的 join 方法,它用來等待線程的執(zhí)行結(jié)束,如果我們不加 join 方法,它就不會等待 tJavaThread 的執(zhí)行完畢,輸出的結(jié)果可能就不是 10000

      可以看到,在 run 方法還沒有結(jié)束前,run 就被返回了。也就是說,程序不會等到 run 方法執(zhí)行完畢就會執(zhí)行下面的指令。

      使用繼承方式創(chuàng)建線程的優(yōu)勢:編寫比較簡單;可以使用 this 關(guān)鍵字直接指向當前線程,而無需使用 Thread.currentThread()來獲取當前線程。

      使用繼承方式創(chuàng)建線程的劣勢:在 Java 中,只允許單繼承(拒絕肛精說使用內(nèi)部類可以實現(xiàn)多繼承)的原則,所以使用繼承的方式,子類就不能再繼承其他類。

      使用 Runnable 接口來創(chuàng)建線程

      相對的,還可以使用 Runnable 接口來創(chuàng)建線程,如下示例

      線程的主要創(chuàng)建步驟如下

      • 首先定義 Runnable 接口,并重寫 Runnable 接口的 run 方法,run 方法的方法體同樣是該線程的線程執(zhí)行體。
      • 創(chuàng)建線程實例,可以使用上面代碼這種簡單的方式創(chuàng)建,也可以通過 new 出線程的實例來創(chuàng)建,如下所示

      • 再調(diào)用線程對象的 start 方法來啟動該線程。

      線程在使用實現(xiàn) Runnable 的同時也能實現(xiàn)其他接口,非常適合多個相同線程來處理同一份資源的情況,體現(xiàn)了面向?qū)ο蟮乃枷搿?/p>

      使用 Runnable 實現(xiàn)的劣勢是編程稍微繁瑣,如果要訪問當前線程,則必須使用 Thread.currentThread()方法。

      使用 Callable 接口來創(chuàng)建線程

      Runnable 接口執(zhí)行的是獨立的任務,Runnable 接口不會產(chǎn)生任何返回值,如果你希望在任務完成后能夠返回一個值的話,那么你可以實現(xiàn) Callable 接口而不是 Runnable 接口。Java SE5 引入了 Callable 接口,它的示例如下

      我想,使用 Callable 接口的好處你已經(jīng)知道了吧,既能夠?qū)崿F(xiàn)多個接口,也能夠得到執(zhí)行結(jié)果的返回值。Callable 和 Runnable 接口還是有一些區(qū)別的,主要區(qū)別如下

      • Callable 執(zhí)行的任務有返回值,而 Runnable 執(zhí)行的任務沒有返回值
      • Callable(重寫)的方法是 call 方法,而 Runnable(重寫)的方法是 run 方法。
      • call 方法可以拋出異常,而 Runnable 方法不能拋出異常

      使用線程池來創(chuàng)建線程

      首先先來認識一下頂級接口 Executor,Executor 雖然不是傳統(tǒng)線程創(chuàng)建的方式之一,但是它卻成為了創(chuàng)建線程的替代者,使用線程池的好處如下

      • 利用線程池能夠復用線程、控制最大并發(fā)數(shù)。
      • 實現(xiàn)任務線程隊列緩存策略和拒絕機制。
      • 實現(xiàn)某些與時間相關(guān)的功能,如定時執(zhí)行、周期執(zhí)行等。
      • 隔離線程環(huán)境。比如,交易服務和搜索服務在同一臺服務器上,分別開啟兩個線程池,交易線程的資源消耗明顯要大;因此,通過配置獨立的線程池,將較慢的交易服務與搜索服務隔開,避免個服務線程互相影響。

      你可以使用如下操作來替換線程創(chuàng)建

      new Thread(new(RunnableTask())).start()

      // 替換為

      Executor executor = new ExecutorSubClass() // 線程池實現(xiàn)類;executor.execute(new RunnableTask1());executor.execute(new RunnableTask2());

      ExecutorService 是 Executor 的默認實現(xiàn),也是 Executor 的擴展接口,ThreadPoolExecutor 類提供了線程池的擴展實現(xiàn)。Executors 類為這些 Executor 提供了方便的工廠方法。下面是使用 ExecutorService 創(chuàng)建線程的幾種方式

      CachedThreadPool

      從而簡化了并發(fā)編程。Executor 在客戶端和任務之間提供了一個間接層;與客戶端直接執(zhí)行任務不同,這個中介對象將執(zhí)行任務。Executor 允許你管理異步任務的執(zhí)行,而無須顯示地管理線程的生命周期。

      public static void main(String[] args) {ExecutorService service = Executors.newCachedThreadPool();for(int i = 0;i < 5;i++){service.execute(new TestThread());}service.shutdown();}

      CachedThreadPool 會為每個任務都創(chuàng)建一個線程。

      注意:ExecutorService 對象是使用靜態(tài)的 Executors 創(chuàng)建的,這個方法可以確定 Executor 類型。對 shutDown 的調(diào)用可以防止新任務提交給 ExecutorService ,這個線程在 Executor 中所有任務完成后退出。

      FixedThreadPool

      FixedThreadPool 使你可以使用有限的線程集來啟動多線程

      public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(5);for(int i = 0;i < 5;i++){service.execute(new TestThread());}service.shutdown();}

      有了 FixedThreadPool 使你可以一次性的預先執(zhí)行高昂的線程分配,因此也就可以限制線程的數(shù)量。這可以節(jié)省時間,因為你不必為每個任務都固定的付出創(chuàng)建線程的開銷。

      SingleThreadExecutor

      SingleThreadExecutor 就是線程數(shù)量為 1 的 FixedThreadPool,如果向 SingleThreadPool 一次性提交了多個任務,那么這些任務將會排隊,每個任務都會在下一個任務開始前結(jié)束,所有的任務都將使用相同的線程。SingleThreadPool 會序列化所有提交給他的任務,并會維護它自己(隱藏)的懸掛隊列。

      public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();for(int i = 0;i < 5;i++){service.execute(new TestThread());}service.shutdown();}

      從輸出的結(jié)果就可以看到,任務都是挨著執(zhí)行的。我為任務分配了五個線程,但是這五個線程不像是我們之前看到的有換進換出的效果,它每次都會先執(zhí)行完自己的那個線程,然后余下的線程繼續(xù)走完這條線程的執(zhí)行路徑。你可以用 SingleThreadExecutor 來確保任意時刻都只有唯一一個任務在運行。

      休眠

      影響任務行為的一種簡單方式就是使線程 休眠,選定給定的休眠時間,調(diào)用它的 sleep()方法, 一般使用的 TimeUnit 這個時間類替換 Thread.sleep()方法,示例如下:

      public class SuperclassThread extends TestThread{

      @Overridepublic void run() {System.out.println(Thread.currentThread() + “starting …” );

      try {for(int i = 0;i < 5;i++){if(i == 3){System.out.println(Thread.currentThread() + "sleeping …");TimeUnit.MILLISECONDS.sleep(1000);}}} catch (InterruptedException e) {e.printStackTrace();}

      System.out.println(Thread.currentThread() + “wakeup and end …”);}

      public static void main(String[] args) {ExecutorService executors = Executors.newCachedThreadPool();for(int i = 0;i < 5;i++){executors.execute(new SuperclassThread());}executors.shutdown();}}

      關(guān)于 TimeUnit 中的 sleep() 方法和 Thread.sleep() 方法的比較,請參考下面這篇博客

      ([www.cnblogs.com/xiadongqing…](

      ))

      優(yōu)先級

      上面提到線程調(diào)度器對每個線程的執(zhí)行都是不可預知的,隨機執(zhí)行的,那么有沒有辦法告訴線程調(diào)度器哪個任務想要優(yōu)先被執(zhí)行呢?你可以通過設(shè)置線程的優(yōu)先級狀態(tài),告訴線程調(diào)度器哪個線程的執(zhí)行優(yōu)先級比較高,請給這個騎手馬上派單,線程調(diào)度器傾向于讓優(yōu)先級較高的線程優(yōu)先執(zhí)行,然而,這并不意味著優(yōu)先級低的線程得不到執(zhí)行,也就是說,優(yōu)先級不會導致死鎖的問題。優(yōu)先級較低的線程只是執(zhí)行頻率較低。

      public class SimplePriorities implements Runnable{

      private int priority;

      public SimplePriorities(int priority) {this.priority = priority;}

      @Overridepublic void run() {Thread.currentThread().setPriority(priority);for(int i = 0;i < 100;i++){System.out.println(this);if(i % 10 == 0){Thread.yield();}}}

      @Overridepublic String toString() {return Thread.currentThread() + ” ” + priority;}

      public static void main(String[] args) {ExecutorService service = Executors.newCachedThreadPool();for(int i = 0;i < 5;i++){service.execute(new SimplePriorities(Thread.MAX_PRIORITY));}service.execute(new SimplePriorities(Thread.MIN_PRIORITY));}}

      toString() 方法被覆蓋,以便通過使用 Thread.toString()方法來打印線程的名稱。你可以改寫線程的默認輸出,這里采用了 Thread[pool-1-thread-1,10,main] 這種形式的輸出。

      通過輸出,你可以看到,最后一個線程的優(yōu)先級最低,其余的線程優(yōu)先級最高。注意,優(yōu)先級是在 run 開頭設(shè)置的,在構(gòu)造器中設(shè)置它們不會有任何好處,因為這個時候線程還沒有執(zhí)行任務。

      盡管 JDK 有 10 個優(yōu)先級,但是一般只有 MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY 三種級別。

      作出讓步

      我們上面提過,如果知道一個線程已經(jīng)在 run() 方法中運行的差不多了,那么它就可以給線程調(diào)度器一個提示:我已經(jīng)完成了任務中最重要的部分,可以讓給別的線程使用 CPU 了。這個暗示將通過 yield() 方法作出。

      有一個很重要的點就是,Thread.yield() 是建議執(zhí)行切換 CPU,而不是強制執(zhí)行 CPU 切換。

      對于任何重要的控制或者在調(diào)用應用時,都不能依賴于 yield()方法,實際上, yield() 方法經(jīng)常被濫用。

      后臺線程

      后臺(daemon)線程,是指運行時在后臺提供的一種服務線程,這種線程不是屬于必須的。當所有非后臺線程結(jié)束時,程序也就停止了,**同時會終止所有的后臺線程。**反過來說,只要有任何非后臺線程還在運行,程序就不會終止。

      public class SimpleDaemons implements Runnable{

      @Overridepublic void run() {while (true){try {TimeUnit.MILLISECONDS.sleep(100);System.out.println(Thread.currentThread() + ” ” + this);} catch (InterruptedException e) {System.out.println(“sleep() interrupted”);}}}

      public static void main(String[] args) throws InterruptedException {for(int i = 0;i < 10;i++){Thread daemon = new Thread(new SimpleDaemons());daemon.setDaemon(true);daemon.start();}System.out.println("All Daemons started");TimeUnit.MILLISECONDS.sleep(175);}}

      在每次的循環(huán)中會創(chuàng)建 10 個線程,并把每個線程設(shè)置為后臺線程,然后開始運行,for 循環(huán)會進行十次,然后輸出信息,隨后主線程睡眠一段時間后停止運行。在每次 run 循環(huán)中,都會打印當前線程的信息,主線程運行完畢,程序就執(zhí)行完畢了。因為 daemon 是后臺線程,無法影響主線程的執(zhí)行。

      但是當你把 daemon.setDaemon(true)去掉時,while(true) 會進行無限循環(huán),那么主線程一直在執(zhí)行最重要的任務,所以會一直循環(huán)下去無法停止。

      ThreadFactory

      按需要創(chuàng)建線程的對象。使用線程工廠替換了 Thread 或者 Runnable 接口的硬連接,使程序能夠使用特殊的線程子類,優(yōu)先級等。一般的創(chuàng)建方式為

      class SimpleThreadFactory implements ThreadFactory {public Thread newThread(Runnable r) {return new Thread(r);}}

      Executors.defaultThreadFactory 方法提供了一個更有用的簡單實現(xiàn),它在返回之前將創(chuàng)建的線程上下文設(shè)置為已知值

      ThreadFactory 是一個接口,它只有一個方法就是創(chuàng)建線程的方法

      public interface ThreadFactory {

      // 構(gòu)建一個新的線程。實現(xiàn)類可能初始化優(yōu)先級,名稱,后臺線程狀態(tài)和 線程組等 Thread newThread(Runnable r);}

      下面來看一個 ThreadFactory 的例子

      public class DaemonThreadFactory implements ThreadFactory {

      @Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setDaemon(true);return t;}}

      public class DaemonFromFactory implements Runnable{

      @Overridepublic void run() {while (true){try {TimeUnit.MILLISECONDS.sleep(100);System.out.println(Thread.currentThread() + ” ” + this);} catch (InterruptedException e) {System.out.println(“Interrupted”);}}}

      public static void main(String[] args) throws InterruptedException {ExecutorService service = Executors.newCachedThreadPool(new DaemonThreadFactory());for(int i = 0;i < 10;i++){service.execute(new DaemonFromFactory());}System.out.println("All daemons started");TimeUnit.MILLISECONDS.sleep(500);}}

      Executors.newCachedThreadPool 可以接受一個線程池對象,創(chuàng)建一個根據(jù)需要創(chuàng)建新線程的線程池,但會在它們可用時重用先前構(gòu)造的線程,并在需要時使用提供的 ThreadFactory 創(chuàng)建新線程。

      public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue(),threadFactory);}

      加入一個線程

      一個線程可以在其他線程上調(diào)用 join()方法,其效果是等待一段時間直到第二個線程結(jié)束才正常執(zhí)行。如果某個線程在另一個線程 t 上調(diào)用 t.join() 方法,此線程將被掛起,直到目標線程 t 結(jié)束才回復(可以用 t.isAlive() 返回為真假判斷)。

      也可以在調(diào)用 join 時帶上一個超時參數(shù),來設(shè)置到期時間,時間到期,join 方法自動返回。

      對 join 的調(diào)用也可以被中斷,做法是在線程上調(diào)用 interrupted 方法,這時需要用到 try…catch 子句

      public class TestJoinMethod extends Thread{

      @Overridepublic void run() {for(int i = 0;i < 5;i++){try {TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {System.out.println("Interrupted sleep");}System.out.println(Thread.currentThread() + " " + i);}}

      public static void main(String[] args) throws InterruptedException {TestJoinMethod join1 = new TestJoinMethod();TestJoinMethod join2 = new TestJoinMethod();TestJoinMethod join3 = new TestJoinMethod();

      join1.start();// join1.join();

      join2.start();join3.start();}}

      join() 方法等待線程死亡。 換句話說,它會導致當前運行的線程停止執(zhí)行,直到它加入的線程完成其任務。

      線程異常捕獲

      由于線程的本質(zhì),使你不能捕獲從線程中逃逸的異常,一旦異常逃出任務的 run 方法,它就會向外傳播到控制臺,除非你采取特殊的步驟捕獲這種錯誤的異常,在 Java5 之前,你可以通過線程組來捕獲,但是在 Java 5 之后,就需要用 Executor 來解決問題,因為線程組不是一次好的嘗試。

      下面的任務會在 run 方法的執(zhí)行期間拋出一個異常,并且這個異常會拋到 run 方法的外面,而且 main 方法無法對它進行捕獲

      public class ExceptionThread implements Runnable{

      @Overridepublic void run() {throw new RuntimeException();}

      public static void main(String[] args) {try {ExecutorService service = Executors.newCachedThreadPool();service.execute(new ExceptionThread());

      小伙伴們有興趣想了解內(nèi)容和更多相關(guān)學習資料的請點贊收藏+評論轉(zhuǎn)發(fā)+關(guān)注我,后面會有很多干貨。我有一些面試題、架構(gòu)、設(shè)計類資料可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,需要的話歡迎下載!私信我回復【666】即可免費獲取

      作者:Java架構(gòu)追夢

      原文出處:https://xie.infoq.cn/article/ba889a8db90d444d24c67ffbc

      鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
      用戶投稿
      上一篇 2022年6月22日 09:20
      下一篇 2022年6月22日 12:01

      相關(guān)推薦

      聯(lián)系我們

      聯(lián)系郵箱:admin#wlmqw.com
      工作時間:周一至周五,10:30-18:30,節(jié)假日休息