競態條件
當某一個計算的值依賴于多個線程交替執行時序時,就會發生競態條件。換句話說,就是正確的結果取決于運氣。
復合操作
i++是一個復合操作,因為這其中包括了讀取-修改-寫入
處理器如何進行原子操作
1.鎖住總線來保證原子性
所謂總線鎖就是使用處理器提供一個Lock#信號,當一個處理器在總線上輸出這個信號時。其他處理器的請求將會被阻塞,那么該處理器就可以獨占內存
2.鎖住緩存來保證完整性
在同一時刻,我們只需要保證對某一個內存地址的操作是原子性即可,但是總線鎖把CPU和整個內存的通信都鎖住了。
有兩種情況不會使用緩存鎖
1.處理器不支持鎖住緩存
2.操作的數據不能存儲在處理器內部
可重入鎖
當一個線程請求獲得請他線程的鎖時,就會發生阻塞。然而,由于鎖是可重入的,因此一個線程如果試圖獲得一個獲得一個已經由他自己獲得的鎖時,這個請求就會成功,這就是鎖的內置鎖的重入。
重入的一種實現方法時,當一個鎖被一個線程持有時,JVM就記下鎖的持有者,并獲取計數器,置為1.每次重入一次,計數器加一,釋放一次減一。當計數器為0時,線程釋放鎖。
volatile
在Java中,如果一個字段被聲明為了volatile,那么Java內存模型將確保所有的線程看到的這個變量都是一致的。還有,這個變量的讀/寫都將具有原子性
實現原理:被volatile修飾的字段,會比其他的變量多出一條lock前綴指令,前綴指令在多核處理器中會引發兩件事情
1) 將當前處理器緩存行的數據寫回系統內存
2) 這個寫回內存的操作將會使其他CPU里緩存了該內存地址的數據無效。當處理器想要對該數據進行處理使用時,會重新從系統內存中把該數據讀取到處理器緩存中去。
synchronized
同步鎖。在Java SE 1.6以后為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了偏向鎖和輕量級鎖。
Java中任何一個對象都可以作為鎖,具體表現為一下三種形式。
1) 對于普通的方法,鎖住的是當前的對象
2) 對于被static修飾的靜態方法,鎖住的是該對象的Class對象
3) 對于同步代碼塊,鎖住的則是括號中的對象
JVM基于進入和退出Monitor對象來實現方法同步和代碼塊同步。代碼塊同步是使用monitorenter和monitorexit指令來實現的,JVM必須保證每個monitorenter都有一個對應的monitorexit配對。JVM在同步代碼開始的地方插入monitorenter,在異常和同步代碼結束的地方插入monitorexit。任何一個對象都有一個monitor與之關聯,當且一個monitor對象唄持有之后,它將處于鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象的monitor的所有權,即嘗試獲得對象的鎖。
在Java對象頭中存儲著三種內存,分別是Mark Word,Class Medadata Address,Array length
Mark Word :存儲對象的hashCode,鎖信息,以及分代年齡
Class Medadata Address:存儲指向對象類型的地址的指針
Array length:存儲數組的長度(如果當前對象是數組的話)
在Java中鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態,重量級鎖狀態,鎖會隨著競爭情況逐漸升級。只能升級,不能降級。
偏向鎖
偏向鎖在進入和退出同步代碼塊時不需要進行CAS操作來加鎖或者解鎖,只需要測試一下當前對象頭的Mark Word中是否存儲著當前線程的偏向鎖。如果測試成功。表示已經獲取了鎖。如果測試失敗,則需要再次測試一下Mark Word中的偏向鎖標志位是否設置為1(表示當前是偏向鎖),如果沒有設置,就采用CAS競爭鎖。如果沒有設置,則嘗試使用CAS將對象頭中的偏向鎖設置為當前線程
偏向鎖的撤銷:偏向鎖采用了一種需要競爭才會釋放鎖的機制。當其他線程競爭鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷需要在全局安全點執行(這個時間點沒有字節碼在執行)。它會首先暫停擁有偏向鎖的線程,然后去檢查偏向鎖的線程是否還活著,如果死亡,則將鎖的對象頭設置為無鎖狀態。如果線程任然活著,那么擁有偏向鎖的棧想會被執行,遍歷對象的鎖記錄。棧中的鎖記錄和對象中的Mark Word要么重新偏向其他線程,要么標記為無鎖狀態或者標記對象不適合偏向鎖。
上面的意思是,先暫停持有偏向鎖的線程,嘗試直接切換。如果不成功,就繼續運行,并且標記對象不適合偏向鎖,鎖膨脹(鎖升級)。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
輕量級鎖
線程首先在棧幀中創建用于存儲鎖對象對象頭中的鎖記錄的空間,并且將Mark Word中的記錄復制到所記錄中去。然后使用CAS嘗試將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,則表示當前線程獲得了鎖,如果失敗則表示處于競爭狀態,會采用CAS自旋來獲取鎖。
輕量級鎖在解鎖時,會嘗試把對象頭中的指針替換為Mark Word,如果成功,表示已經釋放了鎖。如果失敗,則表示當前處于競爭條件下。鎖就會膨脹為重量級鎖
注意,輕量級鎖會一直保持,喚醒總是發生在輕量級鎖解鎖時。因為加鎖的時候CAS操作已經成功,而CAS失敗的線程會立即鎖膨脹,并且阻塞等待喚醒。
重量級鎖
重量級鎖在線程競爭是不會使用自旋,不會消耗CPU。缺點是線程阻塞,響應時間緩慢。使用的場景在是追求吞吐量和同步代碼中執行速度的時候。
Java線程的中斷
中斷可以理解為一個標志位屬性,它表示其他線程是否對該線程進行了中斷操作。線程通過檢查自身是否被中斷來執行響應,線程通過isInterupt()來判斷是否被中斷。也可以調用靜態方法Thread.interupted()來對當前線程的中斷標志位進行復位。如果現場處于終結狀態,那么即時該線程被中斷過,也會返回false。
線程間的通信
通過volatile和synchronized關鍵字來保證線程之間的共享數據。
通過等待/喚醒機制來通信?
notify() nitifyAll() wait() wait(long) 等待超時的時間 毫秒 wait(long, int) 精確到納秒
生產-消費者模式 一個線程修改了一個對象的值,而另外一個線程感知到了變化,然后進行相應的操作。
Thread.join()
當線程A執行了thread.join()的方法之后表示,當線程A等待thread線程終止之后才從join方法返回。
Lock
Lock與sychronized相比,特性是
1 可以嘗試非阻塞式的獲取鎖? ? ? ?tryLock()
2 能夠中斷式的獲取鎖,獲取到鎖的線程能夠響應中斷,當獲取到鎖的線程被中斷時,中斷異常會被拋出,同時會釋放鎖。 lockInterruptibly();
3 能夠超時獲取鎖 tryLock(long time, TimiUnit unit);