筆記:Java多線程

線程簡述

進程是一個程序的一個運行中的實例,os爲進程分配內存、IO等資源。進程擁有若干個(至少一個)線程來得到CPU執行其中的指令,線程使用進程的資源。java

使用

線程的典型使用流程以下:android

  1. 實現Runnable接口,具體的執行邏輯。
  2. 建立一個Thread對象,Runable做爲它的任務。
  3. 調用start方法開啓線程。
  4. 任務完成或發生異常後線程終止。

不推薦繼承Thread類的方式,尤爲是Thread有相似線程池那樣的管理機制時。應該把線程的管理和任務的提交分別對待。express

一般本身能夠爲Runable類設置標記字段來取消或者暫停線程的執行等,但線程機制有本身的一套功能實現這些。api

線程的狀態

能夠調用getState方法得到當前線程對象的運行狀態,包括:安全

  • NEW
    The thread has been created, but has never been started.網絡

  • RUNNABLE
    可運行,或正在運行中。(啓動後,或每次從新得到CPU時間時)。數據結構

  • WAITING
    線程(調用await)等待條件成立,以後被其它線程(調用signalAll)喚醒。併發

  • TIMED_WAITING
    計時等待:包括Thread.sleep,Thread.join,Object.wait,Lock.tryLock,Condition.await。異步

  • BLOCKED
    線程等待鎖而阻塞。async

  • TERMINATED
    run方法正常執行結束或發生異常而終止。

線程的生命週期

線程的生命週期表現爲其幾種狀態之間的轉換:

線程狀態轉換

使用線程是爲了異步地執行任務,雖然能夠限定線程的執行時間,可是一般地線程的執行週期是未知的。一個線程完成其run方法的正常退出後就結束了,此外,若run方法執行期間發生異常,那麼線程也當即終止。

線程達到終止有下面的方式:

正常結束

  • 被動結束:
    線程執行完畢。

  • 主動方式:
    能夠結合標記字段,從設計上讓線程的代碼退出。但這不是最好的,對於循環執行的任務,只能在當前循環以後判斷標記而後是否繼續運行。更有的線程任務,只能作到「不可打斷的原子操做——例如一個讀取db的操做,網絡請求」執行後在結束時根據標記來放棄後續處理(通常也就是不執行回調)。從代碼設計上終止線程很難達到當即的目標。

Thread類提供了stop方法來「當即,強制」終止線程,但這是不合理的:
在沒法預知當前線程的邏輯執行的程度時終止線程會致使相關狀態的不一致。

Thread提供了interrupt()和isInterrupted()來到達上述的「標記」字段的目標,並且更具優點。由於interrupt()可讓一些非運行狀態下的線程(sleep、wait、await、tryLock等)拋出InterruptedException異常來讓run方法得到有關「中斷」的通知。
不過:若是執行了一些自己就阻塞的語句,如db讀寫和網絡請求等,中斷是沒法當即被響應的。

異常結束

自身邏輯的執行拋出了異常,那麼線程當即中斷。能夠爲線程設置異常處理器。

線程的設置

未捕獲異常處理

  • 因爲run方法未聲明任何已檢查異常,因此Thread子類run方法中不能拋出任何已檢查異常。當拋出未檢查異常時,線程終止。

  • 可使用setUncaughtExceptionHandler方法爲線程指定一個異常處理器,作一些記錄日誌這樣的處理。Thread.setDefaultUncaughtExceptionHandler靜態方法能夠爲全部線程指定默認的異常處理器。

線程優先級

線程優先級決定了OS對線程調度的優先順序。Thread類提供了MAX_PRIORITY = 十、MIN_PRIORITY = 1和NORM_PRIORITY = 5三個優先級常量。默認狀況下線程自動得到啓動它的線程的優先級,使用setPriority(int priority)方法來改變優先級,priority是介於MAX_PRIORITY和MIN_PRIORITY之間的整數。
具體的OS劃分的線程優先級數量是不一樣的,例如android的api 21版本中,系統就提供了10個不一樣的優先級常量(在android.os.Process.THREAD_PRIORITY_xxx)。JVM在運行時會將設置的priority映射到對應OS提供的優先級上,有可能不一樣的priority值最終對應相同的優先級。因此不要依賴優先級的數值來保證線程運行的嚴格順序。

守護線程

守護線程的目標是爲其它線程服務,能夠調用setDaemon(boolean isDaemon)方法在啓動線程前設置它爲守護線程。若是進程中只剩下守護線程,那麼其運行狀態和沒有任何線程在進行中是同樣的,系統隨時會終止進程,也就是說守護線程在任什麼時候候均可以被中斷。由於它隨時可能被終止——finally塊的執行也沒法保證,因此不要在其中獲取須要釋放的資源。

線程同步

被多個線程共同訪問的變量「通常是」須要同步。線程內部的變量(方法局部變量)是無需同步的,由於各個線程本身有一份,而像成員變量須要考慮同步。

變量須要同步的緣由:線程對變量的操做不是原子的。
下面的:

count++;

發生了三步操做:

  1. 讀取count的值到線程私有的寄存器。
  2. count加1。
  3. 寄存器中的count寫回原count。

更多的場景下,多個操做一塊兒發生(語句塊或整個方法體),涉及到若干個狀態的讀取和賦值,此時須要的同步已經不是簡單的基本類型變量的訪問了,而是保證操做集合的同步(有序)執行。
因此,同步的最終結果是狀態的正確性,可是同步自己是針對語句(代碼塊)進行的。

Lock和Condition

鎖用來保護代碼塊被同步訪問。爲須要同步處理的狀態準備一個鎖對象,以後將代碼塊放入鎖對象的lock/unlock之間就可讓全部線程去競爭鎖,每次只有一個線程得到鎖並執行對應的代碼塊。

若是被同步的代碼塊的執行須要某個先決條件,可使用鎖對象建立一個關聯的Condition對象來達到此目的(一個鎖能夠建立多個關聯的條件對象)。

條件對象的await()方法使得當前線程轉爲阻塞狀態,並當即釋放鎖。以後有其它線程調用該條件對象的signalAll()方法後(通知線程,但不保證條件必定成立)阻塞的線程再次轉爲可運行,再次得到鎖後await()方法返回(Each thread must re-acquire the lock before it can return from await()),以後還須要繼續斷定條件。

爲了實現異步任務等待條件成立的目標時,await-signalAll方式要比本身構造while-sleep這樣的代碼要好得多,由於sleep的間隔很差控制,消耗較大,並且不夠及時。

鎖和條件的的標準使用方式以下:

private boolean conditionSatisfied;
private ReentrantLock mLock = new ReentrantLock();
private Condition mCondition = mLock.newCondition();

private void syncMethod() throws InterruptedException {
    mLock.lock();
    try {
        while (!conditionSatisfied) {
            // await()返回後應該繼續判斷條件,進而繼續等待或執行
            mCondition.await();
        }

        // ...
        // 須要同步的代碼塊
        // ...

        // 狀態向着條件可能成立的方向發展了,通知其它線程
        mCondition.signalAll();
    } finally {
        mLock.unlock();
    }
}

注意:

  • 鎖是可重進的,線程能夠重複得到已得到鎖,這樣須要鎖的方法的代碼中能夠繼續執行須要該鎖的其它方法。鎖內部維護「得到/釋放」的計數。

  • await()使得線程等待期間(waiting)可能發生InterruptedException異常,它和線程的stop有關,須要同步的方法通常聲明拋出就好了。

  • java的Object類中有一些成員使得每一個對象都有一個默認的鎖,並關聯一個默認的條件對象。爲方法添加synchronized關鍵字正是使用了此內部鎖完成同步的,至關於爲方法體整個添加lock/unlock(等同synchronized(this))。很顯然對象的全部同步方法都會一塊兒被同步。靜態方法使用類型對象的內部鎖,全部靜態同步方法一塊兒被同步

同步方法或synchronized(this)中使用wait/notifyAll完成await/signalAll相同的工做。只是一種語法上的簡化,在其它地方調用wait/notifyAll會引起IllegalMonitorStateException異常——由於沒有得到關聯的內部對象鎖。

上面的代碼能夠簡化爲:

private synchronized void syncMethod() throws InterruptedException {
    while (!conditionSatisfied) {
        wait();
    }

    // ...
    // 是須要同步的代碼塊
    // ...

    notifyAll();
}

NOTE:相比Lock-Condition的結構,synchronized方法有一些侷限性(好比對象鎖能夠被外界獲取)。從使用上看,應該優先使用java.util.concurrent包下的類型完成同步任務,其次是考慮同步方法,由於它足夠簡單。最後,僅當目標功能的複雜性須要Lock-Condition時纔去使用它。

@

同步的設計知足「木桶原理」,必須讓全部的訪問入口都加以同步,不然同步是不完整的無效的。

@

鎖用來管理那些試圖進入synchronized方法的線程,條件用來管理那些調用wait的線程。

  • 使用volatile關鍵字修飾變量後,它保證了多個線程訪問的變量是內存中的同一個(線程能夠在寄存器中保持變量的副本)。這樣,原子操做(讀取和寫入)就「天然獲得同步」了。不過非原子操做(!,n++,n=n+1)是沒法被同步的,例以下面的方法flipCanceled是非同步的:
volatile boolean canceled;
volatile int count;
public void flipCanceled() {
  // 讀取 + 寫入 2個操做
  canceled = !canceled;
  count = count + 1;
}

「簡單變量的同步」可使用java.util.concurrent.atomic下的類型完成,其中使用了不少機器指令而不是鎖完成同步。

volatile自己不提供同步,它修飾變量,保證多個線程訪問同一個內存。而鎖/synchronized保護代碼塊的同步執行。

  • 死鎖
    鎖和條件都有可能引發死鎖,因爲鎖的競爭而引發的死鎖從代碼的同步設計上分析排查來避免。而條件問題可能和運行時的數據相關,最終全部線程都在等待條件,這樣的bug更須要仔細設計來避免。

建議使用notifyAll、signalAll而避免notify、signal的使用。tryLock、帶超時限制的tryLock、await也能夠很大程度上避免死鎖。相比lock方法的阻塞性質——沒法被interrupted,tryLock和await均可以被中斷來解除死鎖。

  • 線程局部變量
    對非線程安全的對象進行同步會下降效率,線程安全的對象每每也很低效。對應一個被共享訪問的變量,可使用同步來保證安全,或者不須要共享時替換爲在代碼塊中使用局部變量,這又帶來了速度和內存上的消耗,而ThreadLocal 能夠爲每一個線程提供獨立的一份變量。

雖然能夠本身設計線程類來保持須要的變量,不過好處是明顯的,在完成針對不一樣線程建立不一樣變量對像的目標時,變量像普通的成員變量那樣被定義,而不須要干涉具體的線程類。

  • 讀寫鎖
    讀取頻繁,寫較少。併發讀取,互斥寫入。ReentrantReadWriteLock。

  • 阻塞隊列
    Lock、Condition是同步機制的底層工具,阻塞隊列能夠以數據結構的形式在高層次上完成多個線程之間的協同。生產者線程和消費者線程分別存放和讀取隊列中的任務數據,在隊列滿和空的時候阻塞。

優先使用java.util.concurrent包下提供了經常使用集合數據結構的線程安全的版本。Collections.synchronized系列靜態方法能夠將已有的非線程安全的集合包裝爲線程安全的版本。

高級類型

詳細內容在用到時查閱包java.util.concurrent的API package summary文檔。

任務和結果表示

  • Callable
  • Future
  • FutureTask

執行器

  • Interfaces
    Executor is a simple standardized interface for defining custom thread-like subsystems, including thread pools, asynchronous I/O, and lightweight task frameworks. Depending on which concrete Executor class is being used, tasks may execute in a newly created thread, an existing task-execution thread, or the thread calling execute, and may execute sequentially or concurrently. ExecutorService provides a more complete asynchronous task execution framework. An ExecutorService manages queuing and scheduling of tasks, and allows controlled shutdown. The ScheduledExecutorService subinterface and associated interfaces add support for delayed and periodic task execution. ExecutorServices provide methods arranging asynchronous execution of any function expressed as Callable, the result-bearing analog of Runnable. A Future returns the results of a function, allows determination of whether execution has completed, and provides a means to cancel execution. A RunnableFuture is a Future that possesses a run method that upon execution, sets its results.
  • Implementations
    Classes ThreadPoolExecutor and ScheduledThreadPoolExecutor provide tunable, flexible thread pools. The Executors class provides factory methods for the most common kinds and configurations of Executors, as well as a few utility methods for using them. Other utilities based on Executors include the concrete class FutureTask providing a common extensible implementation of Futures, and ExecutorCompletionService, that assists in coordinating the processing of groups of asynchronous tasks.

Class ForkJoinPool provides an Executor primarily designed for processing instances of ForkJoinTask and its subclasses. These classes employ a work-stealing scheduler that attains high throughput for tasks conforming to restrictions that often hold in computation-intensive parallel processing.

主要類型

  • Executors
public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newCachedThreadPool();
public static ExecutorService newSingleThreadExecutor();
  • Executor
public interface Executor {
    void execute(Runnable command);
}
  • ExecutorService
    public interface ExecutorService extends Executor.
  • AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService
shutdown
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
invokeAll、invokeAny
  • ThreadPoolExecutor
    ThreadPoolExecutor extends AbstractExecutorService.

使用步驟

1)調用Executors的newXxxThreadPool獲得線程池。
2)調用submit提交Runnable或Callable。
3)保存返回的Future。
4)沒有後續任務執行時shutdown。

同步器

線程同步問題有一些典型的稱做同步器的數據結構,包括:

  • Semaphore is a classic concurrency tool.
  • CountDownLatch is a very simple yet very common utility for blocking until a given number of signals, events, or conditions hold.
  • A CyclicBarrier is a resettable multiway synchronization point useful in some styles of parallel programming.
  • A Phaser provides a more flexible form of barrier that may be used to control phased computation among multiple threads.
  • An Exchanger allows two threads to exchange objects at a rendezvous point, and is useful in several pipeline designs.

資料

  • Java核心技術 卷1 基礎知識 原書第9版

(本文是由Atom編寫 10/18/2016 6:28:00 PM)

相關文章
相關標籤/搜索