進程是一個程序的一個運行中的實例,os爲進程分配內存、IO等資源。進程擁有若干個(至少一個)線程來得到CPU執行其中的指令,線程使用進程的資源。java
線程的典型使用流程以下:android
不推薦繼承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++;
發生了三步操做:
更多的場景下,多個操做一塊兒發生(語句塊或整個方法體),涉及到若干個狀態的讀取和賦值,此時須要的同步已經不是簡單的基本類型變量的訪問了,而是保證操做集合的同步(有序)執行。
因此,同步的最終結果是狀態的正確性,可是同步自己是針對語句(代碼塊)進行的。
鎖用來保護代碼塊被同步訪問。爲須要同步處理的狀態準備一個鎖對象,以後將代碼塊放入鎖對象的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 boolean canceled; volatile int count; public void flipCanceled() { // 讀取 + 寫入 2個操做 canceled = !canceled; count = count + 1; }
「簡單變量的同步」可使用java.util.concurrent.atomic
下的類型完成,其中使用了不少機器指令而不是鎖完成同步。
volatile自己不提供同步,它修飾變量,保證多個線程訪問同一個內存。而鎖/synchronized保護代碼塊的同步執行。
建議使用notifyAll、signalAll而避免notify、signal的使用。tryLock、帶超時限制的tryLock、await也能夠很大程度上避免死鎖。相比lock方法的阻塞性質——沒法被interrupted,tryLock和await均可以被中斷來解除死鎖。
雖然能夠本身設計線程類來保持須要的變量,不過好處是明顯的,在完成針對不一樣線程建立不一樣變量對像的目標時,變量像普通的成員變量那樣被定義,而不須要干涉具體的線程類。
讀寫鎖
讀取頻繁,寫較少。併發讀取,互斥寫入。ReentrantReadWriteLock。
阻塞隊列
Lock、Condition是同步機制的底層工具,阻塞隊列能夠以數據結構的形式在高層次上完成多個線程之間的協同。生產者線程和消費者線程分別存放和讀取隊列中的任務數據,在隊列滿和空的時候阻塞。
優先使用
java.util.concurrent
包下提供了經常使用集合數據結構的線程安全的版本。Collections.synchronized系列靜態方法能夠將已有的非線程安全的集合包裝爲線程安全的版本。
詳細內容在用到時查閱包java.util.concurrent的API package summary文檔。
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.
public static ExecutorService newFixedThreadPool(int nThreads); public static ExecutorService newCachedThreadPool(); public static ExecutorService newSingleThreadExecutor();
public interface Executor { void execute(Runnable command); }
public interface ExecutorService extends Executor.
public abstract class AbstractExecutorService implements ExecutorService shutdown <T> Future<T> submit(Callable<T> task); Future<?> submit(Runnable task); invokeAll、invokeAny
1)調用Executors的newXxxThreadPool獲得線程池。
2)調用submit提交Runnable或Callable。
3)保存返回的Future。
4)沒有後續任務執行時shutdown。
線程同步問題有一些典型的稱做同步器的數據結構,包括:
(本文是由Atom編寫 10/18/2016 6:28:00 PM)