《Java 編程思想》讀書筆記之併發(二)

基本的線程機制

併發編程使咱們能夠將程序劃分爲多個分離的、獨立運行的任務。經過使用多線程機制,這些獨立的任務(也被稱爲子任務)中的每個都將由「執行線程」來驅動。一個線程就是在進程中的一個單一的順序控制流。java

在使用線程時,CPU 將輪流給每一個任務分配其佔用時間。每一個任務都以爲本身在一直佔用 CPU,但事實上 CPU 時間是劃分紅片斷分配給了全部任務(此爲單 CPU 狀況,多 CPU 確實是同時執行)。編程

使用線程機制是一種創建透明的、可擴展的程序的方法,若是程序運行得太慢,爲機器增添一個 CPU 就能很容易地加快程序的運行速度。多任務和多線程每每是使用多處理器系統的最合理的方式。大數據的分佈式擴展思想與之相似,當程序性能不行時,能夠經過擴展集羣提升程序併發度提升性能,可是不準修改代碼。多線程

定義任務

線程能夠驅動任務,而描述任務須要必定的方式,java 中建議的方式是實現 Runnable 接口,其次是繼承 Thread 類。如下是兩種方式的代碼實現:併發

Runnable 接口實現

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...");
    }

}

Thread 繼承實現

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...");
    }

}

使用 Executor

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_PRIORITYNORM_PRIORITYMIN_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()方法處理未捕獲的異常。這一般是在一組線程以一樣方式處理未捕獲異常時使用,若不一樣的線程須要有不一樣的異常處理方式,則最好在線程內部單獨處理。

相關文章
相關標籤/搜索