第一章 多線程技能
一、多線程的建立
1、內建Thread類
2、實作Runnable接口
二、線程的方法
1、currentThread()
擷取目前運作的線程。
該方法将傳回代碼段正在被哪個線程調用的資訊。
2、isAlive()
判斷目前的線程是否處于活動狀态。
如果線程未start(),傳回false;
如果線程start(),未運作結束,傳回true;
如果線程已經運作結束,傳回false;
3、sleep()
在指定的毫秒數内讓目前“正在執行的線程”休眠(暫停執行)。
這個“正在執行的線程”是指 this.currentThread() 傳回的線程。
4、getId()
擷取線程的唯一辨別。
三、停止線程的相關方法
1、interrupt()
該方法僅僅是在目前線程打了一個标記,标記為中斷狀态,并不是真的停止線程。
如果想要停止線程,需要自己線上程類run()方法中通過擷取目前線程的狀态,進行判斷和處理。
在sleep()狀态下interrupt線程,會抛出InterruptedException,并清除interrupt狀态。
先把線程打上interrupt()标記,再讓線程sleep(),同樣會抛出InterruptedException,并清除interrupt狀态。
總結:隻要一個線程同時滿足interrupt()狀态和sleep()狀态,就會抛出InterruptedException,并且會清除interrupt狀态。
2、interrupted()
測試目前線程是否已經中斷。
執行本方法後,将會自動清除掉目前線程的interrupt()狀态,即将已經打過interrupt()标記的線程,變為沒有interrupt()标記。
3、isInterrupted()
測試線程是否已經中斷。不會改變interrupt()狀态。
4、異常法
在run()方法中,通過判斷this.interrupted(),抛出一個異常,以此打斷線程的正常執行順序。
同時,在run()方法中,try()catch()異常。
5、暴力停止——stop()
該方法已經被廢棄。該方法将會抛出ThreadDeath異常,此異常不需要顯示地捕捉。
廢棄原因:
①有可能使一些請理性的工作得不到完成
②對鎖定的對象進行了“解鎖”,導緻資料得不到同步的處理,出現資料不一緻的問題。
6、return
與interrupt()配合使用,和異常法相似。
更加推薦使用異常法,因為過多的return将會污染程式,而使用異常流能更好、更友善地控制程式的運作流程。
四、暫停線程的相關方法
1、suspend()
使一個線程進入暫停狀态,可恢複運作。
suspend()狀态的線程isAlive()為true。
2、resume()
讓一個陷入暫停狀态的線程恢複運作。
3、suspend()和resume()的缺點
①獨占
在使用公共的同步對象,也就是加鎖對象時,由于自身陷入了暫停狀态,鎖得不到釋放,其他線程也就無法擷取加鎖對象了。
例:System.out.println();
②不同步
因為線程的暫停而導緻資料不同步。
方法執行一半被暫停了,方法的目的是改變對象的兩個屬性,結果剛改變了一個屬性就被暫停,導緻了資料的不同步。
4、yield()
放棄目前的CPU資源,将它讓給其他的任務去占用CPU執行時間。
有可能剛剛放棄,馬上又獲得CPU時間片。
五、線程的優先級
1、setPriority()
線程可以劃分優先級,優先級較高的線程得到的CPU資源較多。
設定線程優先級有助于幫“線程規劃期”确定下一次選擇哪一個線程來優先執行。
在Java中,線程的優先級分為1~10這10個等級,如果小于1或者大于10,則JDK将會抛出異常throw new IllegalArgumentException()。
JDK中使用三個常量來預置定義優先級的值,代碼如下:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
2、繼承特性
在Java中,線程的優先級具有繼承性,比如A線程啟動B線程,則B線程的優先級與A是一樣的。
3、規則性
CPU盡量将執行資源讓給優先級比較高的線程。
4、随機性
線程的優先級與代碼執行順序無關。
優先級較高的線程不一定先執行完。
六、守護線程
Java線程中有兩種線程,一種是使用者線程,另一種就是守護(Daemon)線程。
1、什麼是守護線程?
守護線程是一種特殊的線程,它的特性有陪伴的含義,當程序中不存在非守護線程了,則守護線程自動銷毀。
典型的守護線程就是垃圾回收線程(GC),當程序中沒有非守護線程了,則垃圾回收線程也就沒有存在的必要了,自動銷毀。
第二章 對象及變量的并發通路
一、synchronized同步方法
1、A線程先持有object對象的Lock鎖,B線程可以以異步的方式調用object對象中的非synchronized類型的方法。
2、A線程先持有object對象的Lock鎖,B線程如果需要在這時調用object對象中的synchronized類型的方法則需等待,也就是同步。
3、髒讀:隻對指派操作加鎖,未對取值操作加鎖。
4、synchronized鎖重入
①前提
-
- 不在函數内使用靜态或全局資料。
- 不傳回靜态或全局資料,所有資料都由函數的調用者提供。
- 使用本地資料(工作記憶體),或者通過制作全局資料的本地拷貝來保護全局資料。
- 不調用不可重入函數
②可重入與線程安全
可重入的函數一定是線程安全的,反之則不一定成立。
③synchronized可重入鎖的實作
每個鎖關聯一個線程持有者和一個計數器。當計數器為0時表示該鎖沒有被任何線程持有,那麼任何線程都可能獲得該鎖而調用相應方法。當一個線程請求成功後,JVM會記下持有鎖的線程,并将計數器計為1。此時其他線程請求該鎖,則必須等待。而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增。當線程退出一個synchronized方法/塊時,計數器會遞減,如果計數器為0則釋放該鎖。
④可重入鎖具有繼承性
當存在父子類繼承關系時,子類是完全可以通過“可重入鎖”調用父類的同步方法的。
這裡的繼承性,不是指的方法,是指的鎖。
5、出現異常,鎖自動釋放
6、同步不具有繼承性
父類某個方法加同步鎖,子類繼承父類,子類無鎖。
二、synchronized同步語句塊
1、synchronized(this)代碼塊是鎖定目前對象的
2、可以将任意對象作為對象螢幕
3、靜态同步synchronized方法與synchronized(class)代碼塊
①synchronized加到static靜态方法上是給Class類上鎖。
②synchronized加到非static靜态方法上是給對象上鎖。
4、不使用String作為鎖對象
5、同步synchronized方法無限等待與解決
如果一個類有多個方法都持有目前對象當鎖,若其中一個方法進入死循環,其他同步對象将陷入無限等待。
解決:都用new Object()當鎖,方法之間異步。
6、死鎖問題
①死鎖是程式設計的Bug,在設計程式時就要避免雙方互相持有對方的鎖的情況。
②死鎖問題與鎖是否嵌套無關,隻要互相等待對方釋放鎖就有可能出現死鎖。
在jdk安裝目錄的bin目錄下,執行jps檢視目前運作的main方法所在類的程序id,
再輸入jstack -l id檢視執行結果。
7、内部類與靜态内部類
synchronized綁定了相同對象就是同步的,綁定不同對象就是異步的。
針對同步方法,隻要對象不變,即使對象的屬性被改變,運作的結果還是同步。
三、volatile關鍵字
關鍵字volatile的主要作用是使變量在多個線程間可見。
線程安全包含原子性和可見性兩個方面。volatile解決的是可見性,但是在JDK1.7之前才有用。
- volatile重要工作是避免線程髒讀:當線程對volatile變量進行讀操作時,會先将自己工作記憶體中的變量置為無效,之後再通過主記憶體拷貝新值到工作記憶體中使用。
- volatile解決的是變量在多個線程之間的可見性,但不能完全保證資料的原子性。
- 現在JVM經過優化,已不會出現liveness failure 。是以沒事别用volatile。
第三章 線程間通信
一、等待/通知機制
1、wait()
釋放鎖,可以加時間自動喚醒。
2、notify()
随機喚醒一個以參數為鎖的線程,但是在目前線程的方法執行完之前,不會釋放鎖。
3、notifyAll()
喚醒所有線程。
4、通過管道進行線程間的通信:位元組流
在Java的JDK中提供了4個類來使線程間可以進行通信:
1)PipedInputStream 和 PipedOutputStream
2)PipedReader 和 PipedWriter
二、join的使用
在很多情況下,主線程建立并啟動子線程,如果子線程中要進行大量的耗時運算,主線程往往将早于子線程結束之前結束。這時,如果主線程想等待子線程執行完成之後再結束,比如子線程處理一個資料,主線程要取得這個資料中的值,就要用到join()方法了。
方法join()的作用是等待線程對象銷毀。
1、作用
方法join的作用,是使對象x正常執行run()方法中的任務,而使目前線程z進行無限期的阻塞,等待線程x銷毀後再繼續執行線程z後面的代碼。
方法join具有使線程排隊運作的作用,有些類似同步的運作效果。
2、join與synchronized的差別是:
join在内部使用wait()方法進行等待,而sychronized關鍵字使用的是“對象螢幕”原理做為同步。
3、join()方法與interrupt()方法如果彼此遇到,則會出現異常。
4、join(long)具有釋放鎖的特點。
三、類ThreadLocal的使用
變量值的共享可以使用public static變量的形式,所有的線程都使用同一個public static 變量。如果想實作每一個線程都有自己的共享變量該如何解決呢?JDK中提供的類ThreadLocal正是為了解決這個問題。
類ThreadLocal主要解決的就是每個線程綁定自己的值,可以将ThreadLocal類比喻成全局存放資料的盒子,盒子中可以存儲每個線程的私有資料。
1、ThreadLocal解決的是變量在不同線程間的隔離性,也就是不同線程擁有自己的值,不同線程中的值可以放入ThreadLock類中進行儲存。
2、get()
預設傳回null,可以通過繼承ThreadLocal重寫initialValue()方法的方式,修改預設傳回值。
四、類InheritableThreadLocal的使用
使用類InheritableThreadLocal可以在子線程中取得父線程繼承下來的值。
第四章 Lock的使用
一、使用ReentrantLock類
1、擷取鎖的方法
a) lock(), 如果擷取了鎖立即傳回,如果别的線程持有鎖,目前線程則一直處于休眠狀态,直到擷取鎖;
b) tryLock(), 如果擷取了鎖立即傳回true,如果别的線程正持有鎖,立即傳回false;
c) tryLock(long timeout,TimeUnit unit), 如果擷取了鎖定立即傳回true,如果别的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果擷取了鎖定,就傳回true,如果等待逾時,傳回false;
d) lockInterruptibly:如果擷取了鎖定立即傳回,如果沒有擷取鎖定,目前線程處于休眠狀态,直到或者鎖定,或者目前線程被别的線程中斷;
2、unlock()方法
釋放鎖,最好放在finally代碼塊中執行。
3、Condition對象
- Object類中的wait()方法,相當于Condition類中的await()方法。二者都會釋放鎖。
- Object類中的wait(long timeout)方法相當于Condition類中的await(long time, TimeUnit unit)方法。
- Object類中的notify()方法相當于Condition類中的signal()方法。notify()不會釋放鎖,signal()會釋放鎖。
- Object類中的notifyAll()方法相當于Condition類中的signalAll()方法。
4、公平鎖與非公平鎖
公平鎖:線程擷取鎖的順序是按照線程加鎖的順序來配置設定的,即先來先得的FIFO先進先出順序。
非公平鎖:搶占機制,随機獲得鎖。
通過new ReentrantLock(true)可以擷取公平鎖,new ReentrantLock(false)可以擷取非公平鎖。
new ReentrantLock()是非公平鎖。
5、方法的講解
- int getHoldCount():查詢目前線程保持此鎖定的個數,也就是調用lock()方法的次數。
- int getQueueLength():傳回正在等待擷取此鎖定的線程估計數。比如有5個線程,1個線程執行await()方法,那麼在調用getQueueLength()方法後,傳回值是4,說明有4個線程同時在等待lock的釋放。
- int getWaitQueueLength(Condition condition):傳回等待與次鎖定相關的給定條件Condition的線程估計數。比如有5個線程,每個線程都執行了同一個condition對象的await()方法,則調用本方法的傳回值是5。
- boolean hasQueuedThread(Thread thread):查詢指定的線程是否正在等待擷取此鎖定。
- boolean hasQueuedThread():查詢是否有線程正在等待擷取此鎖定。
- boolean hasWaiters(Condition condition):查詢是否有線程正在等待與此鎖定有關的condition條件。
- boolean isFair():判斷是不是公平鎖。
- boolean isHeldByCurrentThread():查詢目前線程是否保持此鎖定。
- boolean isLocked():查詢此鎖定是否由任意線程保持。
- void lockInterruptibly():如果目前線程未被中斷,則擷取鎖定,如果已經被中斷則出現異常。
- boolean tryLock():僅在調用時鎖定未被另一個線程保持的情況下,才擷取該鎖定。
- boolean tryLock(long timeout, TimeUnit unit):如果鎖定在給定等待時間内沒有被另一個線程保持,且目前線程未被中斷,則擷取該鎖定。
- void awaitUninterruptibly():使用await()方法,且不會抛出InterruptedException異常。
- boolean awaitUntil(Date deadline):指定時間自己喚醒自己,也可以被提前喚醒。
二、使用ReentrantReadWriteLock類
1、讀讀共享
2、寫寫互斥
3、讀寫互斥
4、寫讀互斥