併發編程使咱們能夠將程序劃分爲多個分離的、獨立運行的任務。經過使用多線程機制,這些獨立的任務(也被稱爲子任務)中的每個都將由「執行線程」來驅動。一個線程就是在進程中的一個單一的順序控制流。java
在使用線程時,CPU 將輪流給每一個任務分配其佔用時間。每一個任務都以爲本身在一直佔用 CPU,但事實上 CPU 時間是劃分紅片斷分配給了全部任務(此爲單 CPU 狀況,多 CPU 確實是同時執行)。編程
使用線程機制是一種創建透明的、可擴展的程序的方法,若是程序運行得太慢,爲機器增添一個 CPU 就能很容易地加快程序的運行速度。多任務和多線程每每是使用多處理器系統的最合理的方式。大數據的分佈式擴展思想與之相似,當程序性能不行時,能夠經過擴展集羣提升程序併發度提升性能,可是不準修改代碼。多線程
線程能夠驅動任務,而描述任務須要必定的方式,java 中建議的方式是實現 Runnable 接口,其次是繼承 Thread 類。如下是兩種方式的代碼實現:併發
public class MyTask implements Runnable { @Override public void run() { System.out.println(Thread.currentThread() + ": running..."); } public static void main(String[] args) { Thread t = new Thread(new MyTask()); t.start(); System.out.println(Thread.currentThread() + ": running..."); } }
public class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread() + ": running..."); } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); System.out.println(Thread.currentThread() + ": running..."); } }
Java SE5 的 java.util.concurrent 包中的執行器(Executor)將爲你管理 Thread 對象,從而簡化了併發編程。其實就是 Java 的線程池,它大大的減小的對於線程的管理,包括線程的建立、銷燬等,而且它還能複用已經建立的線程對象,減小因爲反覆建立線程引發的開銷,即節省了資源,同時也提升了程序的運行效率。這部份內容比較重要,以後會單獨開一篇介紹,此處就介紹到這。分佈式
實現 Runnable 接口只能執行任務,沒法得到任務的返回值。若是但願得到返回值,則應該實現 Callable 接口,而且應該使用 ExecutorService.submit() 方法調用它。下面是一個示例:ide
public class MyCallableTask implements Callable<String> { private int id; public MyCallableTask(int id) { this.id = id; } @Override public String call() throws Exception { return "Result : " + Thread.currentThread() + ": " + id; } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); List<Future<String>> futures = new ArrayList<>(); for (int i = 0; i < 10; i++) { futures.add(exec.submit(new MyCallableTask(i))); } for (Future<String> future : futures) { try { // 調用 future 方法會致使線程阻塞,直到 future 對應的線程執行完畢 System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { exec.shutdown(); } } } }
submit() 方法會產生 Future 對象,而且使用泛型的方式對返回值類型進行了定義。調用 Future.get() 方法,會致使線程阻塞,直到被調用的線程執行完畢返回結果,固然 java 也提供了設置超時時間的 get() 方法,防止線程一直阻塞,或者你也能夠調用 Future 的 isDone() 方法預先判斷線程是否執行完畢,再調用 get() 獲取返回值。性能
「休眠」就是使任務停止執行指定的時間。在 Java 中能夠經過Thread.sleep()
方法來實現,JDK 1.6 以後推薦使用TimeUnit
來實現任務的休眠。大數據
對sleep()
方法的調用會拋出InterruptedException
異常,因爲異常不能跨線程傳播,所以必須在本地處理任務內部產生的異常。this
線程間雖然能夠切換,可是並無固定的順序可言,所以,若要控制任務執行的順序,絕對不要寄但願於線程的調度機制。操作系統
線程的優先級是用來控制線程的執行頻率的,優先級高的線程執行頻率高,但這並不會致使優先級低的線程得不到執行,僅僅是下降執行的頻率。
在絕大多數時間裏,全部線程都應該以默認的優先級運行。試圖操縱線程優先級一般是一種錯誤。——《Java 編程思想》
你能夠在一個任務的內部,經過調用Thread.currentThread()
來得到對驅動該任務的 Thread 對象的引用。
儘管 JDK 有 10 個優先級,但它與多數操做系統都不能映射得很好。所以在手動指定線程優先級的時候儘可能只使用MAX_PRIORITY
,NORM_PRIORITY
,MIN_PRIORITY
三種級別。
經過調用Thread.yield()
方法可使當前線程主動讓出 CPU,同時向系統建議具備「相同優先級」的其餘線程能夠運行(只是一個建議,沒有任何機制保證它必定會被採納)。所以,對於任何重要的控制或在調整應用時,都不能依賴於yield()
。
所謂後臺(daemon)線程,是指在程序運行的時候在後臺提供一種通用服務的線程,而且這種線程並不屬於程序中不可或缺的部分。——《Java 編程思想》
所以,當全部的非後臺線程結束時,程序也就停止了,同時會殺死進程中的全部後臺線程。
必須在線程啓動以前調用setDeamon()
方法,才能把它設置爲後臺線程。
能夠經過調用isDaemon()
方法來肯定線程是不是一個後臺線程。若是是一個後臺線程,那麼它建立的任何線程將被自動設置成後臺線程。
若是在後臺線程中有finally{}
語句塊,當全部非後臺程序執行結束時,後臺線程會忽然終止,並不會執行finally{}
語句塊中的內容,後臺線程不會有任何開發者但願出現的結束確認形式。由於不能以優雅的方式來關閉後臺線程,因此它們幾乎不是一種好的思想。
若是某個線程在另外一個線程 t 上調用t.join()
,此線程將被掛起,直到目標線程 t 結束才恢復(即t.isAlive()
返回爲 false)。也能夠在調用join()
時帶上一個超時參數,在目標線程處理超時時join()
方法總能返回。
對join()
方法的調用能夠被中斷,只要在調用線程上調用interrupt()
方法。
因爲線程的本質特性,一旦在run()
方法中未捕捉異常,那麼異常就會向外傳播到控制檯,除非採起特殊的步驟捕獲這種錯誤的異常。
在 Java SE5 以前,可使用線程組來捕獲這些異常(不推薦),在 Java SE5 以後能夠用 Executor 來解決這個問題。Executor 容許修改產生線程的方式,容許你在每一個 Thread 對象上都附着一個異常處理器Thread.UncaughtExceptionHandler
,此異常處理器會在線程因未捕獲的異常而臨近死亡時被調用uncaughtException()
方法處理未捕獲的異常。這一般是在一組線程以一樣方式處理未捕獲異常時使用,若不一樣的線程須要有不一樣的異常處理方式,則最好在線程內部單獨處理。