引言
學習java.util.concurrent
包下的類,建議按照以下順序進行,這樣可以逐步理解併發程式設計的基本概念和高階特性:
基礎執行緒機制:
Thread
: 雖然不在java.util.concurrent
包中,但理解基本的Thread
類是開始學習併發程式設計的基礎。Runnable
和Callable
: 瞭解如何定義可以線上程中執行的任務。基本的任務執行框架:
Executor
和Executors
: 學習如何使用這些介面和類來管理執行緒的建立和任務的提交。ExecutorService
: 深入瞭解如何管理任務的生命週期,包括關閉執行緒池。同步工具類:
Future
: 理解如何表示非同步計算的結果,並等待執行完成。Semaphore
,CountDownLatch
,CyclicBarrier
,Exchanger
,Phaser
: 學習這些同步輔助類,瞭解它們如何協調多個執行緒間的合作。併發集合:
ConcurrentHashMap
: 學習如何使用執行緒安全的Map實現。ConcurrentLinkedQueue
,CopyOnWriteArrayList
: 瞭解不同的執行緒安全集合及其適用場景。鎖機制:
Lock
和ReentrantLock
: 學習如何使用顯式鎖來控制同步。ReadWriteLock
和ReentrantReadWriteLock
: 理解如何使用讀寫鎖來提高併發讀取效能。原子變數:
AtomicInteger
,AtomicLong
,AtomicReference
等: 掌握如何使用原子變數進行無鎖程式設計。高階併發工具:
ThreadPoolExecutor
和ScheduledThreadPoolExecutor
: 深入理解執行緒池的工作原理和如何自定義執行緒池的行為。CompletionService
: 學習如何管理非同步任務的執行和結果收集。Fork/Join框架:
ForkJoinPool
和RecursiveTask
: 學習分而治之的併發策略,以及如何利用這個框架來提高平行計算的效率。
概述
併發程式設計是Java開發中不可或缺的一部分,它允許開發者編寫能夠充分利用多核處理器效能的應用程式。本文從Java併發程式設計的基石——Thread
類、Runnable
介面以及Callable
介面入手,詳細解釋瞭如何使用它們來建立和管理執行緒。討論瞭如何正確啟動和停止執行緒,如何等待執行緒的完成,以及為什麼不能重啟一個已經執行結束的執行緒。
正文
在現代軟件開發中,利用多核處理器的能力透過併發程式設計提高應用效能已經成為一項必備技能。Java作為一門歷史悠久的程式語言,提供了一套豐富的併發程式設計工具,其中Thread
類、Runnable
介面和Callable
介面是最基礎的元件。本文將深入理解這些元件的使用方法和最佳實踐。
學習基礎執行緒機制時,理解Thread
類、Runnable
介面和Callable
介面的工作原理是至關重要的。這些構建塊為在Java中進行併發程式設計提供了基礎。
Thread 類
Thread
類代表了一個執行緒的例項。在Java中,執行緒是程式中的一個獨立執行路徑。每個執行緒都有自己的程式計數器、棧和區域性變數,但可以訪問共享的記憶體空間和物件。
當建立了Thread
類的一個例項並呼叫它的start()
方法時,JVM會為這個新執行緒分配資源,並呼叫它的run()
方法來執行指定的程式碼。
建立和啟動執行緒
package com.dereksmart.crawling.core; /** * @Author code_up * @Date 2024/7/29 8:55 * @Description Thread測試類 */ public class MyThread extends Thread { public void run() { // Code that executes on the new thread System.out.println("Hello,Derek Smart"); } } class TMain { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // This will call MyThread's run() method on a new thread } }
停止執行緒
class MyThread1 extends Thread { public void run() { int i = 0; while (!interrupted()) { // 執行任務 System.out.println("Hello,Derek Smart1"); } } } MyThread1 thread1 = new MyThread1(); thread1.start(); // This will call MyThread's run() method on a new thread Thread.sleep(1); thread1.interrupt(); // 請求中斷
等待執行緒完成
myThread.join(); // 在當前執行緒中等待myThread執行緒完成
Runnable 介面
Runnable
介面應該由任何類實現,如果例項打算透過某個執行緒執行。它是一個函式式介面,定義了一個無引數的方法run()
。
實現Runnable
介面允許類不必繼承Thread
類就能在新執行緒中執行。可以建立一個實現了Runnable
介面的例項,並將它作為引數傳遞給Thread
類的構造器。
建立和啟動執行緒
package com.dereksmart.crawling.core; /** * @Author code_up * @Date 2024/7/29 8:55 * @Description Runnable測試類 */ public class MyRunnable implements Runnable { public void run() { // Code that executes on the new thread System.out.println("Hello,Derek Smart"); } } class RMain { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); // This will call MyRunnable's run() method on a new thread } }
Callable 介面
Callable
介面是一個泛型介面,定義了一個返回值的call()
方法,並且可以丟擲異常。Callable
介面通常用於那些需要返回結果的場景。
與Runnable
不同,Callable
的call()
方法可以返回一個值,並且可以丟擲一個異常。Callable
任務需要提交給ExecutorService
,它在執行後返回一個Future
物件,透過這個Future
物件可以獲取Callable
的返回值。
建立和啟動執行緒
package com.dereksmart.crawling.core; import java.util.concurrent.*; /** * @Author code_up * @Date 2024/7/29 8:55 * @Description Callable測試類 */ public class MyCallable implements Callable<String> { public String call() throws Exception { return "Callable completed"; } } class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(1); Future<String> future = executorService.submit(new MyCallable()); // 等待執行完成並獲取結果 String result = future.get(); // 阻塞直到任務完成 try { future.get(1000,TimeUnit.MINUTES); }catch (InterruptedException e){ }catch (ExecutionException e){ }catch (TimeoutException e){ } future.cancel(true); System.out.println(result); future.cancel(true); // 關閉執行緒池 // executorService.shutdown(); } }
停止執行緒
Callable任務一旦開始執行,就不能直接中斷。但是,可以透過Future
呼叫cancel(true)
方法來嘗試取消它,如果任務正在執行,會嘗試中斷它。
future.cancel(true); // 嘗試取消正在執行的任務
等待執行緒完成
使用Future.get()
方法等待Callable
任務完成。如果任務已經完成,這個方法會立即返回結果;否則,它會阻塞直到任務完成。
String result = future.get(); // 阻塞直到任務完成
等待執行緒完成 超時機制
使用Future.get()
方法等待Callable
任務完成。如果任務已經完成,這個方法會立即返回結果;否則,它會阻塞直到任務完成。
try { future.get(1000,TimeUnit.MINUTES); //1秒超時報錯 }catch (InterruptedException e){ }catch (ExecutionException e){ }catch (TimeoutException e){ }
重啟執行緒
在Java中,執行緒一旦完成執行就不能重新啟動。如果需要再次執行任務,需要建立一個新的執行緒例項。
其他核心方法
Thread.sleep(long millis)
: 當前執行緒暫停指定的毫秒數。Thread.yield()
: 提示執行緒排程器當前執行緒願意放棄其當前使用的處理器。這是對執行緒排程器的一個提示,排程器可能會忽略這個提示。Thread.currentThread()
: 返回當前正在執行的執行緒物件的引用。Thread.setDaemon(boolean on)
: 將執行緒標記為守護執行緒或使用者執行緒。守護執行緒是指在後臺為其他執行緒提供服務的執行緒,如垃圾回收執行緒。
請注意,Thread.stop()
方法已經被廢棄,因為它是不安全的。正確的停止執行緒的方式是使用中斷或者設定一個標誌變數。
原理
Thread: 每個
Thread
物件代表一個執行執行緒。start()
方法會告訴JVM啟動一個新執行緒,這個新執行緒會呼叫run()
方法。Thread
類提供了管理執行緒生命週期的方法,比如interrupt()
,join()
,sleep()
,yield()
等。Runnable:
Runnable
介面允許將任務的定義與執行分離。它沒有返回值,也不能丟擲檢查型異常。它可以用於建立可以執行在Thread
上的任務。Callable:
Callable
介面與Runnable
類似,但它可以返回一個結果,並且可以丟擲檢查型異常。Callable
任務通常用於那些需要返回值的場景,並且需要提交給ExecutorService
來執行。
在Java中,建立執行緒的兩種常見方式是使用Thread
類和實現Runnable
介面。下面是各自的優缺點:
使用 Thread 類優點:
簡單直觀:繼承
Thread
類並重寫run
方法的方式簡單直觀,對於新手來說容易理解。直接控制執行緒:由於
Thread
類本身控制執行緒的執行,因此可以直接呼叫start
,interrupt
等方法來管理執行緒。
缺點:
不支援多重繼承:在Java中,繼承了
Thread
類就不能再繼承其他類,這限制了類的靈活性。資源消耗較大:每個執行緒都是一個重量級的物件,涉及與作業系統的互動。
擴充套件性差:如果需要將執行緒的執行邏輯與
Thread
類的管理分開,或者需要將執行緒邏輯與執行緒池等併發工具結合使用,直接使用Thread
類就不那麼方便了。
實現 Runnable 介面
優點:
分離任務和執行:
Runnable
介面只代表一個要執行的任務,它不控制執行緒的生命週期。這使得任務程式碼可以被多個執行者(如執行緒或執行緒池)重用。更好的資源共享:實現
Runnable
的類更容易共享資源。多個執行緒可以接收同一個Runnable
例項,並且可以訪問相同的資源,無需建立多個副本。更高的擴充套件性:由於
Runnable
是一個介面,你的類可以實現Runnable
同時還可以繼承其他類,提供了更好的擴充套件性。適用於併發工具:
Runnable
介面與java.util.concurrent
包中的併發工具相容,可以與ExecutorService
等高階併發工具一起使用,方便管理執行緒生命週期和任務執行。
缺點:
無法直接控制執行緒:因為
Runnable
只是任務的抽象,它本身不提供對執行緒的直接控制,如中斷或獲取執行緒狀態等操作。這些需要在Thread
例項上進行。需要額外的步驟建立執行緒:實現
Runnable
後,還需要建立一個Thread
例項並將Runnable
傳遞給它,然後才能啟動執行緒。
實現 Callable 介面
Callable
是類似於Runnable
的介面,但它允許任務返回值,並且可以丟擲異常。
優點:
有返回值:
Callable
可以返回執行結果,這是Runnable
無法提供的。能丟擲異常:與
Runnable
不同,Callable
中的call()
方法允許丟擲異常,使得錯誤處理更加靈活。
缺點:
需要配合Future使用:爲了獲取
Callable
任務的返回值,通常需要使用Future
物件,這增加了程式設計的複雜性。不直接與Thread關聯:和
Runnable
一樣,Callable
任務需要提交給ExecutorService
,由執行緒池管理執行。
總的來說,實現Runnable
或Callable
介面通常比繼承Thread
類更靈活,特別是在使用執行緒池和執行器框架的現代併發程式設計中。然而,直接使用Thread
類在某些簡單的情況下可能更方便,尤其是當需要直接管理執行緒的生命週期時。
結論
透過本文的學習,瞭解了Java併發程式設計的基礎,並掌握瞭如何使用Thread
、Runnable
和Callable
來建立併發程式。同時,也認識到了現代併發工具的重要性,並學會了如何將它們應用於實際開發中,以實現更高效、可靠的併發解決方案。隨著對這些工具的深入理解,將能夠編寫出更加健壯和高效能的Java應用程式。