Java多线程知识小抄集(一)
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
歡迎跳轉到本文的原文鏈接:https://honeypps.com/java/java-multiple-thread-summary-1/
本文主要整理博主遇到的Java多線程的相關知識點,適合速記,故命名為“小抄集”。本文沒有特別重點,每一項針對一個多線程知識做一個概要性總結,也有一些會帶一點例子,習題方便理解和記憶。
1. interrupted與isInterrupted的區(qū)別
interrupted():測試當前線程是否已經是中斷狀態(tài),執(zhí)行后具有狀態(tài)標志清除為false的功能。
isInterrupted():測試線程Thread對象是否已經是中斷狀態(tài),但不清除狀態(tài)標志。
方法:
2. 終止正在運行的線程的三種方法:
- 使用退出標志,是線程正常退出,也就是當run方法完成后線程終止;
- 使用stop方法強行終止線程,但是不推薦使用這個方法,因為stop和suspend及resume一樣都是作廢過期的方法,使用它們可能產生不可預料的結果;
- 使用interrupt方法中斷線程;(推薦)
3. yield方法
yield()方法的作用是放棄當前的CPU資源,將它讓給其他的任務去占用CPU執(zhí)行時間。但放棄時間不確定,有可能剛剛放棄,馬上又獲得CPU時間片。這里需要注意的是yield()方法和sleep方法一樣,線程并不會讓出鎖,和wait不同。
4. 線程的優(yōu)先級
Java中線程的優(yōu)先級分為1-10這10個等級,如果小于1或大于10則JDK拋出IllegalArgumentException()的異常,默認優(yōu)先級是5。在Java中線程的優(yōu)先級具有繼承性,比如A線程啟動B線程,則B線程的優(yōu)先級與A是一樣的。注意程序正確性不能依賴線程的優(yōu)先級高低,因為操作系統(tǒng)可以完全不理會Java線程對于優(yōu)先級的決定。
5. Java中線程的狀態(tài)
New, Runnable, Blocked, Waiting, Time_waiting, Terminated.
6. 守護線程
Java中有兩種線程,一種是用戶線程,另一種是守護線程。當進程中不存在非守護線程了,則守護線程自動銷毀。通過setDaemon(true)設置線程為后臺線程。注意thread.setDaemon(true)必須在thread.start()之前設置,否則會報IllegalThreadStateException異常;在Daemon線程中產生的新線程也是Daemon的;在使用ExecutorSerice等多線程框架時,會把守護線程轉換為用戶線程,并且也會把優(yōu)先級設置為Thread.NORM_PRIORITY。在構建Daemon線程時,不能依靠finally塊中的內容來確保執(zhí)行關閉或清理資源的邏輯。更多詳細內容可參考《Java守護線程概述》
7. synchronized的類鎖與對象鎖
類鎖:在方法上加上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
有關Java中的鎖的詳細信息,可以參考《Java中的鎖》
8. 同步不具備繼承性
當一個線程執(zhí)行的代碼出現異常時,其所持有的鎖會自動釋放。同步不具有繼承性(聲明為synchronized的父類方法A,在子類中重寫之后并不具備synchronized的特性)。
9. wait, notify, notifyAll用法
只能在同步方法或者同步塊中使用wait()方法。在執(zhí)行wait()方法后,當前線程釋放鎖(這點與sleep和yield方法不同)。調用了wait函數的線程會一直等待,知道有其他線程調用了同一個對象的notify或者notifyAll方法才能被喚醒,需要注意的是:被喚醒并不代表立刻獲得對象的鎖,要等待執(zhí)行notify()方法的線程執(zhí)行完,即退出synchronized代碼塊后,當前線程才會釋放鎖,而呈wait狀態(tài)的線程才可以獲取該對象鎖。
如果調用wait()方法時沒有持有適當的鎖,則拋出IllegalMonitorStateException,它是RuntimeException的一個子類,因此,不需要try-catch語句進行捕獲異常。
notify方法只會(隨機)喚醒一個正在等待的線程,而notifyAll方法會喚醒所有正在等待的線程。如果一個對象之前沒有調用wait方法,那么調用notify方法是沒有任何影響的。
詳細可以參考《JAVA線程間協作:wait.notify.notifyAll》
帶參數的wait(long timeout)或者wait(long timeout, int nanos)方法的功能是等待某一時間內是否有線程對鎖進行喚醒,如果超過這個時間則自動喚醒。
10. 管道
在Java中提供了各種各樣的輸入/輸出流Stream,使我們能夠很方便地對數據進行操作,其中管道流(pipeStream)是一種特殊的流,用于在不同線程間直接傳送數據。一個線程發(fā)送數據到輸出管道,另一個線程從輸入管道中讀數據,通過使用管道,實現不同線程間的通信,而無須借助類似臨時文件之類的東西。在JDK中使用4個類來使線程間可以進行通信:PipedInputStream, PipedOutputStream, PipedReader, PipedWriter。使用代碼類似inputStream.connect(outputStream)或outputStream.connect(inputStream)使兩個Stream之間產生通信連接。
幾種進程間的通信方式
- 管道( pipe ):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。
- 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關系進程間的通信。
- 信號量( semophore ) : 信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內不同線程之間的同步手段。
- 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中并由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點。
- 信號 ( sinal ) : 信號是一種比較復雜的通信方式,用于通知接收進程某個事件已經發(fā)生。
- 共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創(chuàng)建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。
- 套接字( socket ) : 套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用于不同及其間的進程通信。
11. join方法
如果一個線程A執(zhí)行了thread.join()語句,其含義是:當前線程A等待thread線程終止之后才從thread.join()返回。join與synchronized的區(qū)別是:join在內部使用wait()方法進行等待,而synchronized關鍵字使用的是“對象監(jiān)視器”做為同步。
join提供了另外兩種實現方法:join(long millis)和join(long millis, int nanos),至多等待多長時間而退出等待(釋放鎖),退出等待之后還可以繼續(xù)運行。內部是通過wait方法來實現的。
可以參考一下一個例子:
System.out.println("method main begin-----");Thread t = new Thread(new Runnable(){int i = 0;@Overridepublic void run(){while(true){System.out.println(i++);try{TimeUnit.MILLISECONDS.sleep(100);}catch (InterruptedException e){e.printStackTrace();}}}});t.start();t.join(2000);System.out.println("method main end-----");運行結果:
method main begin----- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 method main end----- 19 20 2112.ThreadLocal
ThreadLocal可以實現每個線程綁定自己的值,即每個線程有各自獨立的副本而互相不受影響。一共有四個方法:get, set, remove, initialValue。可以重寫initialValue()方法來為ThreadLocal賦初值。如下:
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){@Overrideprotected Long initialValue(){return System.currentTimeMillis();}};ThreadLocal建議設置為static類型的。
使用類InheritableThreadLocal可以在子線程中取得父線程繼承下來的值。可以采用重寫childValue(Object parentValue)方法來更改繼承的值。
查看案例:
運行結果:
Main: get value = 1461585405704 Thread-0: get value = 1461585405704 which plus in subThread.如果去掉@Override protected Object childValue(Object parentValue)方法運行結果:
Main: get value = 1461585396073 Thread-0: get value = 1461585396073注意:在線程池的情況下,在ThreadLocal業(yè)務周期處理完成時,最好顯式的調用remove()方法,清空”線程局部變量”中的值。正常情況下使用ThreadLocal不會造成內存溢出,弱引用的只是threadLocal,保存的值依然是強引用的,如果threadLocal依然被其他對象強引用,”線程局部變量”是無法回收的。
13. ReentrantLock
ReentrantLock提供了tryLock方法,tryLock調用的時候,如果鎖被其他線程持有,那么tryLock會立即返回,返回結果為false;如果鎖沒有被其他線程持有,那么當前調用線程會持有鎖,并且tryLock返回的結果為true。
boolean tryLock() boolean tryLock(long timeout, TimeUnit unit)可以在構造ReentranLock時使用公平鎖,公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的先后順序來一次獲得鎖。synchronized中的鎖時非公平的,默認情況下ReentrantLock也是非公平的,但是可以在構造函數中指定使用公平鎖。
ReentrantLock() ReentrantLock(boolean fair)對于ReentrantLock來說,還有一個十分實用的特性,它可以同時綁定多個Condition條件,以實現更精細化的同步控制。
ReentrantLock使用方式如下:
14. ReentrantLock中的其余方法
- int getHoldCount():查詢當前線程保持此鎖定的個數,也就是調用lock()方法的次數。
- int getQueueLength():返回正等待獲取此鎖定的線程估計數。比如有5個線程,1個線程首先執(zhí)行await()方法,那么在調用getQueueLength方法后返回值是4,說明有4個線程在等待lock的釋放。
- int getWaitQueueLength(Condition condition):返回等待此鎖定相關的給定條件Condition的線程估計數。比如有5個線程,每個線程都執(zhí)行了同一個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():如果當前線程未被中斷,則獲取鎖定,如果已經被中斷則出現異常。
15. Condition
一個Condition和一個Lock關聯在一起,就想一個條件隊列和一個內置鎖相關聯一樣。要創(chuàng)建一個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.
詳細可參考《JAVA線程間協作:Condition》
16. 讀寫鎖ReentrantReadWriteLock
讀寫鎖表示也有兩個鎖,一個是讀操作相關的鎖,也稱為共享鎖;另一個是寫操作相關的鎖,也叫排它鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。在沒有Thread進行寫操作時,進行讀取操作的多個Thread都可以獲取讀鎖,而進行寫入操作的Thread只有在獲取寫鎖后才能進行寫入操作。即多個Thread可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。(lock.readlock.lock(), lock.readlock.unlock, lock.writelock.lock, lock.writelock.unlock)
17. Timer的使用
JDK中的Timer類主要負責計劃任務的功能,也就是在指定時間開始執(zhí)行某一任務。Timer類的主要作用就是設置計劃任務,但封裝任務的類卻是TimerTask類(public abstract class TimerTask extends Object implements Runnable)。可以通過new Timer(true)設置為后臺線程。
有以下幾個方法:
- void schedule(TimerTask task, Date time):在指定的日期執(zhí)行某一次任務。如果執(zhí)行任務的時間早于當前時間則立刻執(zhí)行。
- void schedule(TimerTask task, Date firstTime, long period):在指定的日期之后,按指定的間隔周期性地無限循環(huán)地執(zhí)行某一任務。如果執(zhí)行任務的時間早于當前時間則立刻執(zhí)行。
- void schedule(TimerTask task, long delay):以當前時間為參考時間,在此基礎上延遲指定的毫秒數后執(zhí)行一次TimerTask任務。
- void schedule(TimerTask task, long delay, long period):以當前時間為參考時間,在此基礎上延遲指定的毫秒數,再以某一間隔無限次數地執(zhí)行某一任務。
- void scheduleAtFixedRate(TimerTask task, Date firstTime, long period):下次執(zhí)行任務時間參考上次任務的結束時間,且具有“追趕性”。
TimerTask是以隊列的方式一個一個被順序執(zhí)行的,所以執(zhí)行的時間有可能和預期的時間不一致,因為前面的任務有可能消耗的時間較長,則后面的任務運行時間也會被延遲。
TimerTask類中的cancel方法的作用是將自身從任務隊列中清除。
Timer類中的cancel方法的作用是將任務隊列中的全部任務清空,并且進程被銷毀。
Timer的缺陷:Timer支持基于絕對時間而不是相對時間的調度機制,因此任務的執(zhí)行對系統(tǒng)時鐘變化很敏感,而ScheduledThreadPoolExecutor只支持相對時間的調度。Timer在執(zhí)行所有定時任務時只會創(chuàng)建一個線程。如果某個任務的執(zhí)行時間過長,那么將破壞其他TimerTask的定時精確性。Timer的另一個問題是,如果TimerTask拋出了一個未檢查的異常,那么Timer將表現出糟糕的行為。Timer線程并不波或異常,因此當TimerTask拋出為檢測的異常時將終止定時線程。
JDK5或者更高的JDK中已經很少使用Timer.
18. 線程安全的單例模式
建議不要采用DCL的寫法,建議使用下面這種寫法:
public class LazyInitHolderSingleton { private LazyInitHolderSingleton() { } private static class SingletonHolder { private static final LazyInitHolderSingleton INSTANCE = new LazyInitHolderSingleton(); } public static LazyInitHolderSingleton getInstance() { return SingletonHolder.INSTANCE; } }或者這種:
public enum SingletonClass {INSTANCE; }19. 線程組ThreadGroup
為了有效地對一些線程進行組織管理,通常的情況下事創(chuàng)建一個線程組,然后再將部分線程歸屬到該組中,這樣可以對零散的線程對象進行有效的組織和規(guī)劃。參考以下案例:
ThreadGroup tgroup = new ThreadGroup("mavelous zzh");new Thread(tgroup, new Runnable(){@Overridepublic void run(){System.out.println("A: Begin: "+Thread.currentThread().getName());while(!Thread.currentThread().isInterrupted()){}System.out.println("A: DEAD: "+Thread.currentThread().getName());}}).start();;new Thread(tgroup, new Runnable(){@Overridepublic void run(){System.out.println("B: Begin: "+Thread.currentThread().getName());while(!Thread.currentThread().isInterrupted()){}System.out.println("B: DEAD: "+Thread.currentThread().getName());}}).start();;System.out.println(tgroup.activeCount());System.out.println(tgroup.getName());System.out.println(tgroup.getMaxPriority());System.out.println(tgroup.getParent());TimeUnit.SECONDS.sleep(5);tgroup.interrupt();輸出:
A: Begin: Thread-0 2 mavelous zzh 10 B: Begin: Thread-1 java.lang.ThreadGroup[name=main,maxpri=10] B: DEAD: Thread-1 A: DEAD: Thread-020. 多線程的異常捕獲UncaughtExceptionHandler
setUncaughtExceptionHandler()的作用是對指定線程對象設置默認的異常處理器。
Thread thread = new Thread(new Runnable(){@Overridepublic void run(){int a=1/0;}});thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){@Overridepublic void uncaughtException(Thread t, Throwable e){System.out.println("線程:"+t.getName()+" 出現了異常:"+e.getMessage());}});thread.start();輸出:線程:Thread-0 出現了異常:/ by zero
setDefaultUncaughtExceptionHandler()方法對所有線程對象設置異常處理器。
輸出同上,注意兩者之間的區(qū)別。如果既包含setUncaughtExceptionHandler又包含setDefaultUncaughtExceptionHandler那么會被setUncaughtExceptionHandler處理,setDefaultUncaughtExceptionHandler則忽略。更多詳細信息參考《JAVA多線程之UncaughtExceptionHandler——處理非正常的線程中止》
21.ReentrantLock與synchonized區(qū)別
22. 使用多線程的優(yōu)勢
更多的處理器核心;更快的響應時間;更好的編程模型。
23. 構造線程
一個新構造的線程對象是由其parent線程來進行空間分配的,而child線程繼承了parent線程的:是否為Daemon、優(yōu)先級、加載資源的contextClassLoader以及InheritableThreadLocal(參考第12條),同時還會分配一個唯一的ID來標志這個child線程。
24. 使用多線程的方式
extends Thread 或者implements Runnable
25. 讀寫鎖
讀寫鎖在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖,使得并發(fā)性相比一般的排它鎖有了很大的提升。Java中使用ReentrantReadWriteLock實現讀寫鎖,讀寫鎖的一般寫法如下(修改自JDK7中的示例):
class RWDictionary {private final Map<String, Object> m = new TreeMap<String, Object>();private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();private final Lock r = rwl.readLock();private final Lock w = rwl.writeLock();public Object get(String key){r.lock();try{return m.get(key);}finally{r.unlock();}}public String[] allKeys(){r.lock();try{return (String[]) m.keySet().toArray();}finally{r.unlock();}}public Object put(String key, Object value){w.lock();try{return m.put(key, value);}finally{w.unlock();}}public void clear(){w.lock();try{m.clear();}finally{w.unlock();}}}26.鎖降級
鎖降級是指寫鎖降級成讀鎖。如果當前線程擁有寫鎖,然后將其釋放,最后獲取讀鎖,這種分段完成的過程不能稱之為鎖降級。鎖降級是指把持住(當前擁有的)寫鎖,再獲取到讀鎖,最后釋放(先前擁有的)寫鎖的過程。參考下面的示例:
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();private final Lock r = rwl.readLock();private final Lock w = rwl.writeLock();private volatile static boolean update = false;public void processData(){r.lock();if(!update){//必須先釋放讀鎖r.unlock();//鎖降級從寫鎖獲取到開始w.lock();try{if(!update){//準備數據的流程(略)update = true;}r.lock();}finally{w.unlock();}//鎖降級完成,寫鎖降級為讀鎖}try{//使用數據的流程(略)}finally{r.unlock();}}鎖降級中的讀鎖是否有必要呢?答案是必要。主要是為了保證數據的可見性,如果當前線程不獲取讀鎖而是直接釋放寫鎖,假設此刻另一個線程(T)獲取了寫鎖并修改了數據,那么當前線程無法感知線程T的數據更新。如果當前線程獲取讀鎖,即遵循鎖降級的步驟,則線程T將會被阻塞,直到當前線程使用數據并釋放讀鎖之后,線程T才能獲取寫鎖進行數據更新。
持續(xù)更新中~
博主嘔心瀝血整理發(fā)布,跪求一贊。
wanna more?
Java多線程知識小抄集(二)
Java多線程知識小抄集(三)
參考資料
歡迎跳轉到本文的原文鏈接:https://honeypps.com/java/java-multiple-thread-summary-1/
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
總結
以上是生活随笔為你收集整理的Java多线程知识小抄集(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 设计模式:各个模式间的对比
- 下一篇: Java多线程知识小抄集(二)