本文主要整理部落客遇到的Java多線程的相關知識點,适合速記,故命名為“小抄集”。本文沒有特别重點,每一項針對一個多線程知識做一個概要性總結,也有一些會帶一點例子,習題友善了解和記憶。
interrupted():測試目前線程是否已經是中斷狀态,執行後具有狀态标志清除為false的功能。
isInterrupted():測試線程Thread對象是否已經是中斷狀态,但不清除狀态标志。
方法:
使用退出标志,是線程正常退出,也就是當run方法完成後線程終止;
使用stop方法強行終止線程,但是不推薦使用這個方法,因為stop和suspend及resume一樣都是廢棄過期的方法,使用它們可能産生不可預料的結果;
使用interrupt方法中斷線程;(推薦)
yield()方法的作用是放棄目前的CPU資源,将它讓給其他的任務去占用CPU執行時間。但放棄時間不确定,有可能剛剛放棄,馬上又獲得CPU時間片。這裡需要注意的是yield()方法和sleep方法一樣,線程并不會讓出鎖,和wait不同。
Java中線程的優先級分為1-10這10個等級,如果小于1或大于10則JDK抛出IllegalArgumentException()的異常,預設優先級是5。在Java中線程的優先級具有繼承性,比如A線程啟動B線程,則B線程的優先級與A是一樣的。注意程式正确性不能依賴線程的優先級高低,因為作業系統可以完全不理會Java線程對于優先級的決定。
New, Runnable, Blocked, Waiting, Time_waiting, Terminated.
類鎖:在方法上加上static synchronized的鎖,或者synchronized(xxx.class)的鎖。如下代碼中的method1和method2:
對象鎖:參考method4, method5,method6.
注意方法method4和method5中的同步塊也是互斥的。
下面做一道習題來加深一下對對象鎖和類鎖的了解:
有一個類這樣定義
那麼,有SynchronizedTest的兩個執行個體a和b,對于一下的幾個選項有哪些能被一個以上的線程同時通路呢?
A. a.method1() vs. a.method2()
B. a.method1() vs. b.method1()
C. a.method3() vs. b.method4()
D. a.method3() vs. b.method3()
E. a.method1() vs. a.method3()
答案是什麼呢?BE
當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放。同步不具有繼承性(聲明為synchronized的父類方法A,在子類中重寫之後并不具備synchronized的特性)。
隻能在同步方法或者同步塊中使用wait()方法。在執行wait()方法後,目前線程釋放鎖(這點與sleep和yield方法不同)。調用了wait函數的線程會一直等待,知道有其他線程調用了同一個對象的notify或者notifyAll方法才能被喚醒,需要注意的是:被喚醒并不代表立刻獲得對象的鎖,要等待執行notify()方法的線程執行完,即退出synchronized代碼塊後,目前線程才會釋放鎖,而呈wait狀态的線程才可以擷取該對象鎖。
如果調用wait()方法時沒有持有适當的鎖,則抛出IllegalMonitorStateException,它是RuntimeException的一個子類,是以,不需要try-catch語句進行捕獲異常。
notify方法隻會(随機)喚醒一個正在等待的線程,而notifyAll方法會喚醒所有正在等待的線程。如果一個對象之前沒有調用wait方法,那麼調用notify方法是沒有任何影響的。
帶參數的wait(long timeout)或者wait(long timeout, int nanos)方法的功能是等待某一時間内是否有線程對鎖進行喚醒,如果超過這個時間則自動喚醒。
在Java中提供了各種各樣的輸入/輸出流Stream,使我們能夠很友善地對資料進行操作,其中管道流(pipeStream)是一種特殊的流,用于在不同線程間直接傳送資料。一個線程發送資料到輸出管道,另一個線程從輸入管道中讀資料,通過使用管道,實作不同線程間的通信,而無須借助類似臨時檔案之類的東西。在JDK中使用4個類來使線程間可以進行通信:PipedInputStream, PipedOutputStream, PipedReader, PipedWriter。使用代碼類似inputStream.connect(outputStream)或outputStream.connect(inputStream)使兩個Stream之間産生通信連接配接。
幾種程序間的通信方式 - 管道( pipe ):管道是一種半雙工的通信方式,資料隻能單向流動,而且隻能在具有親緣關系的程序間使用。程序的親緣關系通常是指父子程序關系。 - 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關系程序間的通信。 - 信号量( semophore ) : 信号量是一個計數器,可以用來控制多個程序對共享資源的通路。它常作為一種鎖機制,防止某程序正在通路共享資源時,其他程序也通路該資源。是以,主要作為程序間以及同一程序内不同線程之間的同步手段。 - 消息隊列( message queue ) : 消息隊列是由消息的連結清單,存放在核心中并由消息隊列辨別符辨別。消息隊列克服了信号傳遞資訊少、管道隻能承載無格式位元組流以及緩沖區大小受限等缺點。 - 信号 ( sinal ) : 信号是一種比較複雜的通信方式,用于通知接收程序某個事件已經發生。 - 共享記憶體( shared memory ) :共享記憶體就是映射一段能被其他程序所通路的記憶體,這段共享記憶體由一個程序建立,但多個程序都可以通路。共享記憶體是最快的 IPC 方式,它是針對其他程序間通信方式運作效率低而專門設計的。它往往與其他通信機制,如信号兩,配合使用,來實作程序間的同步和通信。 - 套接字( socket ) : 套解口也是一種程序間通信機制,與其他通信機制不同的是,它可用于不同及其間的程序通信。
如果一個線程A執行了thread.join()語句,其含義是:目前線程A等待thread線程終止之後才從thread.join()傳回。join與synchronized的差別是:join在内部使用wait()方法進行等待,而synchronized關鍵字使用的是“對象螢幕”做為同步。
join提供了另外兩種實作方法:join(long millis)和join(long millis, int nanos),至多等待多長時間而退出等待(釋放鎖),退出等待之後還可以繼續運作。内部是通過wait方法來實作的。
可以參考一下一個例子:
運作結果:
ThreadLocal可以實作每個線程綁定自己的值,即每個線程有各自獨立的副本而互相不受影響。一共有四個方法:get, set, remove, initialValue。可以重寫initialValue()方法來為ThreadLocal賦初值。如下:
ThreadLocal建議設定為static類型的。
使用類InheritableThreadLocal可以在子線程中取得父線程繼承下來的值。可以采用重寫childValue(Object parentValue)方法來更改繼承的值。
檢視案例:
如果去掉@Override protected Object childValue(Object parentValue)方法運作結果:
注意:線上程池的情況下,在ThreadLocal業務周期處理完成時,最好顯式的調用remove()方法,清空”線程局部變量”中的值。正常情況下使用ThreadLocal不會造成記憶體溢出,弱引用的隻是threadLocal,儲存的值依然是強引用的,如果threadLocal依然被其他對象強引用,”線程局部變量”是無法回收的。
ReentrantLock提供了tryLock方法,tryLock調用的時候,如果鎖被其他線程持有,那麼tryLock會立即傳回,傳回結果為false;如果鎖沒有被其他線程持有,那麼目前調用線程會持有鎖,并且tryLock傳回的結果為true。
可以在構造ReentranLock時使用公平鎖,公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的先後順序來一次獲得鎖。synchronized中的鎖時非公平的,預設情況下ReentrantLock也是非公平的,但是可以在構造函數中指定使用公平鎖。
對于ReentrantLock來說,還有一個十分實用的特性,它可以同時綁定多個Condition條件,以實作更精細化的同步控制。
ReentrantLock使用方式如下:
int getHoldCount():查詢目前線程保持此鎖定的個數,也就是調用lock()方法的次數。
int getQueueLength():傳回正等待擷取此鎖定的線程估計數。比如有5個線程,1個線程首先執行await()方法,那麼在調用getQueueLength方法後傳回值是4,說明有4個線程在等待lock的釋放。
int getWaitQueueLength(Condition condition):傳回等待此鎖定相關的給定條件Condition的線程估計數。比如有5個線程,每個線程都執行了同一個condition對象的await方法,則調用getWaitQueueLength(Condition condition)方法時傳回的int值是5。
boolean hasQueuedThread(Thread thread):查詢指定線程是否正在等待擷取此鎖定。
boolean hasQueuedThreads():查詢是否有線程正在等待擷取此鎖定。
boolean hasWaiters(Condition condition):查詢是否有線程正在等待與此鎖定有關的condition條件。
boolean isFair():判斷是不是公平鎖。
boolean isHeldByCurrentThread():查詢目前線程是否保持此鎖定。
boolean isLocked():查詢此鎖定是否由任意線程保持。
void lockInterruptibly():如果目前線程未被中斷,則擷取鎖定,如果已經被中斷則出現異常。
一個Condition和一個Lock關聯在一起,就想一個條件隊列和一個内置鎖相關聯一樣。要建立一個Condition,可以在相關聯的Lock上調用Lock.newCondition方法。正如Lock比内置加鎖提供了更為豐富的功能,Condition同樣比内置條件隊列提供了更豐富的功能:在每個鎖上可存在多個等待、條件等待可以是可中斷的或者不可中斷的、基于時限的等待,以及公平的或非公平的隊列操作。與内置條件隊列不同的是,對于每個Lock,可以有任意數量的Condition對象。Condition對象繼承了相關的Lock對象的公平性,對于公平的鎖,線程會依照FIFO順序從Condition.await中釋放。
注意:在Condition對象中,與wait,notify和notifyAll方法對于的分别是await,signal,signalAll。但是,Condition對Object進行了擴充,因而它也包含wait和notify方法。一定要確定使用的版本——await和signal.
讀寫鎖表示也有兩個鎖,一個是讀操作相關的鎖,也稱為共享鎖;另一個是寫操作相關的鎖,也叫排它鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。在沒有Thread進行寫操作時,進行讀取操作的多個Thread都可以擷取讀鎖,而進行寫入操作的Thread隻有在擷取寫鎖後才能進行寫入操作。即多個Thread可以同時進行讀取操作,但是同一時刻隻允許一個Thread進行寫入操作。(lock.readlock.lock(), lock.readlock.unlock, lock.writelock.lock, lock.writelock.unlock)
JDK中的Timer類主要負責計劃任務的功能,也就是在指定時間開始執行某一任務。Timer類的主要作用就是設定計劃任務,但封裝任務的類卻是TimerTask類(public abstract class TimerTask extends Object implements Runnable)。可以通過new Timer(true)設定為背景線程。
有以下幾個方法:
void schedule(TimerTask task, Date time):在指定的日期執行某一次任務。如果執行任務的時間早于目前時間則立刻執行。
void schedule(TimerTask task, Date firstTime, long period):在指定的日期之後,按指定的間隔周期性地無限循環地執行某一任務。如果執行任務的時間早于目前時間則立刻執行。
void schedule(TimerTask task, long delay):以目前時間為參考時間,在此基礎上延遲指定的毫秒數後執行一次TimerTask任務。
void schedule(TimerTask task, long delay, long period):以目前時間為參考時間,在此基礎上延遲指定的毫秒數,再以某一間隔無限次數地執行某一任務。
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period):下次執行任務時間參考上次任務的結束時間,且具有“追趕性”。
TimerTask是以隊列的方式一個一個被順序執行的,是以執行的時間有可能和預期的時間不一緻,因為前面的任務有可能消耗的時間較長,則後面的任務運作時間也會被延遲。
TimerTask類中的cancel方法的作用是将自身從任務隊列中清除。
Timer類中的cancel方法的作用是将任務隊列中的全部任務清空,并且程序被銷毀。
Timer的缺陷:Timer支援基于絕對時間而不是相對時間的排程機制,是以任務的執行對系統時鐘變化很敏感,而ScheduledThreadPoolExecutor隻支援相對時間的排程。Timer在執行所有定時任務時隻會建立一個線程。如果某個任務的執行時間過長,那麼将破壞其他TimerTask的定時精确性。Timer的另一個問題是,如果TimerTask抛出了一個未檢查的異常,那麼Timer将表現出糟糕的行為。Timer線程并不波或異常,是以當TimerTask抛出為檢測的異常時将終止定時線程。
JDK5或者更高的JDK中已經很少使用Timer.
建議不要采用DCL的寫法,建議使用下面這種寫法:
或者這種:
為了有效地對一些線程進行組織管理,通常的情況下事建立一個線程組,然後再将部分線程歸屬到該組中,這樣可以對零散的線程對象進行有效的組織和規劃。參考以下案例:
輸出:
setUncaughtExceptionHandler()的作用是對指定線程對象設定預設的異常處理器。
輸出:線程:Thread-0 出現了異常:/ by zero
setDefaultUncaughtExceptionHandler()方法對所有線程對象設定異常處理器。
ReentrantLock可以中斷地擷取鎖(void lockInterruptibly() throws InterruptedException)
ReentrantLock可以嘗試非阻塞地擷取鎖(boolean tryLock())
ReentrantLock可以逾時擷取鎖。通過tryLock(timeout, unit),可以嘗試獲得鎖,并且指定等待的時間。
ReentrantLock可以實作公平鎖。通過new ReentrantLock(true)實作。
ReentrantLock對象可以同時綁定多個Condition對象,而在synchronized中,鎖對象的的wait(), notify(), notifyAll()方法可以實作一個隐含條件,如果要和多于一個的條件關聯的對象,就不得不額外地添加一個鎖,而ReentrantLock則無需這樣做,隻需要多次調用newCondition()方法即可。
更多的處理器核心;更快的響應時間;更好的程式設計模型。
一個新構造的線程對象是由其parent線程來進行空間配置設定的,而child線程繼承了parent線程的:是否為Daemon、優先級、加載資源的contextClassLoader以及InheritableThreadLocal(參考第12條),同時還會配置設定一個唯一的ID來标志這個child線程。
extends Thread 或者implements Runnable
讀寫鎖在同一時刻可以允許多個讀線程通路,但是在寫線程通路時,所有的讀線程和其他寫線程均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖,使得并發性相比一般的排它鎖有了很大的提升。Java中使用ReentrantReadWriteLock實作讀寫鎖,讀寫鎖的一般寫法如下(修改自JDK7中的示例):
鎖降級是指寫鎖降級成讀鎖。如果目前線程擁有寫鎖,然後将其釋放,最後擷取讀鎖,這種分段完成的過程不能稱之為鎖降級。鎖降級是指把持住(目前擁有的)寫鎖,再擷取到讀鎖,最後釋放(先前擁有的)寫鎖的過程。參考下面的示例:
鎖降級中的讀鎖是否有必要呢?答案是必要。主要是為了保證資料的可見性,如果目前線程不擷取讀鎖而是直接釋放寫鎖,假設此刻另一個線程(T)擷取了寫鎖并修改了資料,那麼目前線程無法感覺線程T的資料更新。如果目前線程擷取讀鎖,即遵循鎖降級的步驟,則線程T将會被阻塞,直到目前線程使用資料并釋放讀鎖之後,線程T才能擷取寫鎖進行資料更新。
持續更新中~
部落客嘔心瀝血整理釋出,跪求一贊。
wanna more?
<a href="http://blog.csdn.net/u013256816/article/details/51325309">Java多線程知識小抄集(二)</a>
<a href="http://blog.csdn.net/u013256816/article/details/51363643">Java多線程知識小抄集(三)</a>
參考資料
1. 《Java多線程程式設計核心技術》高洪岩著。
2. 《Java并發程式設計的藝術》方騰飛 等著。
3. 《Java并發程式設計實戰》Brian Goetz等著。