Java多線程

|--多線程一定好麼?

cpu密集不好 io密集好

|--如何減少上下文切換:

無鎖併發(數據id根據Hash分段)、CAS、最少線程

|--java線程避免死鎖:

避免一個線程同時有多個鎖

避免一個鎖佔用多個資源

lock.tryLock代替內部鎖

內存屏障:限制命令操作順序,有LoadLoad、LoadStore、LoadStore、StroreStreo四種屏障

緩衝行:cpu緩存最小儲存單位

寫命中:緩存有,直接寫入緩存

緩存一致性:主存改變,其他緩存改變(read、load、use綁定)

順序一致性:單個線程內執行結果一定是不變的(但依然有指令重排,只是結果不受影響的重排)

|--八個CPU原子命令:

lock、unlock、read、load、use、assign、store、write

|--volatile做的事:

1.lock前綴指令使緩存行立即寫入內存(assign、store、write綁定)

2.其他cpu緩存無效

3.加入內存屏障

使用前景:不依賴於上次數據

使用案例:i++:tmp = i;tmp=tmp+1;i = tmp;

64位機器跑32位jvm,long和double:2段分2次計算,不加volatile會導致結果前32位是一個線程結果,後32位一個線程結果

|--synchronized

對象加鎖,Monitor對象,monitorenter和monitorexit命令實現

鎖升級

|--ReentrantLock 可重入鎖

通過CVS等實現,比synchronized效率略高,有公平鎖非公平鎖

鎖可多次進入,並把擁有數++

lock(), 如果獲取了鎖立即返回,如果別的線程持有鎖,當前線程則一直處於休眠狀態,直到獲取鎖

tryLock(), 如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false;

|--ReentrantReadWriteLock

read/write兩把鎖

寫鎖與ReetrantLock類似,只有寫鎖讀鎖都沒被佔用才獲得鎖

讀鎖擁有數是多個線程的,每個線程擁有數只能自己通過ThreadLocal記錄

寫鎖結束降級讀鎖,避免可見性問題

|--Lock和synchronized區別

Lock是通過代碼級實現,cvs

synchronized是通過jvm的monitor實現的

還多了 鎖投票,定時鎖等候和中斷鎖等候等特性

使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情

|--AQS(AbstractQueuedSynchronized)

有隊列,有state、進入會先自旋再阻塞,默認非公平,隊列喚醒了調用tryAcquire,不一定能獲取鎖

|--java對象頭:

MarkWord 長度:32/64,存儲hashCode或者鎖信息

|--CAS unsafe.compareAndSwap(對象地址,原來值,要修改值)

unsafe是通過操作系統實現(CMPXCHG指令),如果失敗返回false;

|--CAS使用:自旋鎖、自適應自旋鎖

|--鎖的升級

偏向鎖(markword指向所在線程,代價低,兩個線程則停安全點撤銷)->輕量級線程(markword置換到擁有者線程,線程對象互指。兩個線程則b線程自旋等待)->重量級鎖(syn、reeentrantLock)

比較:

偏向鎖:  加鎖解鎖消耗極少,鎖競爭的安全點帶來消耗。      適用於一個線程

輕量級鎖:響應快,自旋消耗cpu                                          追求響應時間,同步塊非常快

重量級:                                                                              追求吞吐量,同步塊執行時間長

|--處理器實現原子性策略:

LOCK#信號是一個線程獨佔共享內存(通過鎖住主內存總線,之後優化成緩存鎖)

緩存鎖保證原子性

|--java原子實現:

鎖和CAS

CAS侷限性:ABA問題(過程無感知)、循環時間開銷大、一次保證一個變量

|--內存模型(對底層抽象)

線程通信方式:

內存模型(共享內存)

消息傳遞(A複製到主內存,再從主內存寫到B)

管道:輸入流輸出流(PipedReader,PipedWriter,PipedInputstream,PipedOutputStream)

內存模型:本地內存(共享變量副本、局部變量)、主內存(共享變量)

指令重排序:編譯優化重排、並行重排、內存重排

|--final域重寫規則

構造函數內,final寫入與被構造的對象引用賦值不能重排序(obj=this會引發逃逸,例如此時別的線程調用obj.i,final的i變量還沒初始化)

初次讀含final域對象與隨後讀final區域不能重排

|--單例模式問題

實例化分爲:1.開闢空間memory 2.初始化對象 3.設置instance指向memory。

指令重排可能是:1->3->2 , 若2還未執行,B線程認爲instance非空,直接調用instance,導致錯誤

解決方案:1.volatile禁止重排序 2.匿名內部類(連自己加鎖都不用,類自帶實例化鎖)

|--爲什麼使用多線程

1.多處理器發揮功效

2.更快相應,一個下訂單帶來一系列操作如何快速成功:線程派發,分任務執行

|--java優先級

不一定有用,主要是靠操作系統底層實現

|--interrupt

interrupt不會真的終止,只是一種協作機制

interrupt()將會設置該線程的中斷狀態位,即設置爲true

使用Thread.currentThread().isInterrupted()方法(因爲它將線程中斷標示位設置爲true後,不會立刻清除中斷標示位,即不會將中斷標設置爲false

thread.interrupted()(該方法調用後會將中斷標示位清除,即重新設置爲false

一個線程處於了等待狀態(thread.sleep、thread.join、thread.wait),則在線程在檢查中斷標示時如果發現中斷標示爲true,則會在這些阻塞方法調用處拋出InterruptedException異常,並且在拋出異常後立即將線程的中斷標示位清除,即重新設置爲false。拋出異常是爲了線程從阻塞狀態醒過來,並在結束線程前讓程序員有足夠的時間來處理中斷請求。

鎖的情況下不會被中斷影響

|--阻塞狀態與等待區別

阻塞是進鎖裏,等待是wait、sleep。sleep設置時間狀態叫做超時等待狀態

|--線程的應用

1.等待之後超時

while(結果未返回 && 時間未到)

wait();

2.線程池

要有隊列,狀態

Worker實現Runnable接口,循環從jobs隊列取任務執行,獲取不到就wait();

execute(Job job)時,喚醒jobs

3.基於線程池Web服務器

思路:開一個Socket服務,每次accept後,把這個一對一服務放封裝成job類,放到jobs隊列裏

|--LockSupport

工具類,有park、unpark阻塞喚醒線程

|--Condition

相當於Lock中的wait和notify,區別是wait等待隊列只能有一個,Condition可以有多個

Condition隊列類似於AQS隊列

每個Condition下面有一個等待await的等待隊列

Lock.newCondition()獲取condition

Lock.await(); = wait

Lock.singal();=notify

|--ConcurrentHashMap

問題:HashMap線程不安全導致Entry鏈表編程環,引發死循環。

HashTable效率低

解決:Segment包含HashEntry數組

Segment是一種可重入鎖(ReentrantLock)

實現:segment數量是2的n次方,默認16

每一個segment的容量=每個segment裏HashEntry*負載因子

如何放入數據:再散列確保數據分散後放入segment

get方法:不加鎖,而是用volatile

1.8更新:沒有了segment,橫向用Node鏈表替代,Node被調用取時就synchronize加鎖。當沒Node底下鏈表超過8個,將加鎖

|--ConcurrentLinkedQueue

非阻塞

入隊:定位尾節點,不成功cvs重試(爲了減少CVS,控制尾節點更新頻率)

出隊:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章