我的部落格中有更多後端開發面試題,點我檢視!

并發大全
2萬字參透并發程式設計
-
中線程分為哪些狀态jvm
- 在執行
方法後,線程是不是馬上運作。Thread.start()
多線程導論
- 原子性是指在一個操作中就是
不可以在中途暫停然後再排程,既不被中斷操作,要不執行完成,要不就不執行。cpu
- 可見性是指當多個線程通路同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
- 有序性即程式執行的順序按照代碼的先後順序執行。
多線程設計模式解讀—Producer-Consumer模式
請簡述一下線程的sleep()方法和yield()方法有什麼差別?
-
方法給其他線程運作機會時不考慮線程的優先級,是以會給低優先級的線程以運作的機會;yield()方法隻會給相同優先級或更高優先級的線程以運作的機會sleep()
- 線程執行
方法後轉入阻塞(blocked)狀态,而執行sleep()
方法後轉入就緒(ready)狀态yield()
-
方法聲明抛出InterruptedException,而sleep()
方法沒有聲明任何異常yield()
在Java中wait和seelp方法的不同
-
作用于對象,wait
作用于 Threadsleep
-
釋放對象鎖,wait
不釋放對象鎖sleep
-
運作于同步方法或同步代碼塊,wait
可運作于任何地方sleep
-
不需要捕獲異常,wait
需要捕獲異常sleep
Thread類中的yield方法有什麼作用?
暫停目前線程,将目前線程從
Running
狀态轉到
Ready
狀态,讓出控制權給其他線程
談談 wait / notify 關鍵字的了解
-
wait / notify 存在于所有對象;
使用時需要
,否則synchronized
;IllegalMonitorStateException
- 調用
方法會讓目前線程阻塞,讓出對象鎖;若wait
有設定時間參數,到時間後自動喚醒;wait
-
一次喚醒一個等待的線程;notify
一次性喚醒所有該對象上的等待線程。notifyAll
為什麼wait, notify 和 notifyAll這些方法不在thread類裡面?
wait,notify,notifyAll,sleep這些方法都跟線程的狀态變化有關,為什麼jdk把前三個方法放在Object類裡面,而把sleep放在Thread類裡面?
- Java 内置鎖機制中,鎖是對象級而不是線程級,任何對象都能建立鎖;
- 一個線程可以有多個鎖,若跟線程綁定可能會不夠用。
程序和線程之間的通信方式
- 程序:無名管道、有名管道、信号、共享記憶體、消息隊列、信号量
- 線程:互斥量、讀寫鎖、自旋鎖、線程信号、條件變量
建立線程的方法
- 繼承
類建立線程Thread
- 實作
接口建立線程Runnable
- 使用
和Callable
建立線程Future
- 使用線程池例如用
架構Executor
如何停止一個線程
- 使用退出标志,使線程正常退出,也就是當
方法完成後線程終止。run
- 使用
方法中斷線程。interrupt
- 不推薦使用
方法。相當于電腦斷電關機一樣,是不安全的方法。stop、suspend及resume
線程同步的方法。
同步的實作方面有兩種,分别是
synchronized,wait
與
notify
-
:使一個線程處于等待狀态,并且釋放所持有的對象的lock。wait()
:使一個正在運作的線程處于睡眠狀态,是一個靜态方法,調用此方法要捕捉InterruptedException異常。sleep()
-
:喚醒一個處于等待狀态的線程,注意的是在調用此方法的時候,并不能确切的喚醒某一個等待狀态的線程,而是由JVM确定喚醒哪個線程,而且不是按優先級。notify()
-
:喚醒所有處入等待狀态的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競争。notifyAll()
原子操作的實作原理是通過CAS實作的。
一般問CAS底層原理,我遇到這種問題,直接說比較并更新的過程,底層實作調用了unsafe類,unsafe類是用C++實作的,沒研究過,代碼。糊弄過去的
countDownLatch
countDownLatch
是在java1.5被引入,跟它一起被引入的工具類還有
CyclicBarrier
、
Semaphore
、
concurrentHashMap
和
BlockingQueue
。
-
這個類使一個線程等待其他線程各自執行完畢後再執行。countDownLatch
- 是通過一個計數器來實作的,計數器的初始值是線程的數量。每當一個線程執行完畢後,計數器的值就-1,當計數器的值為0時,表示所有線程都執行完畢,然後在閉鎖上等待的線程就可以恢複工作了。
CountDownLatch和CyclicBarrier差別:
-
是一個計數器,線程完成一個記錄一個,計數器遞減,隻能隻用一次countDownLatch
-
的計數器更像一個閥門,需要所有線程都到達,然後繼續執行,計數器遞增,提供reset功能,可以多次使用CyclicBarrier
Runnable和Thread差別
1.
Runnable
和
Thread
相比優點有:
- 由于Java不允許多繼承,是以實作了
接口可以再繼承其他類,但是Runnable
明顯不可以Thread
-
可以實作多個相同的程式代碼的線程去共享同一個資源,而Runnable
并不是不可以,而是相比于Thread
來說,不太适合,具體原因文章中有。Runnable
當以方式去實作資源共享時,實際上源碼内部是将
Thread
向下轉型為了
Thread
,實際上内部依然是以
Runnable
形式去實作的資源共享
Runnable
2.
Runnable
為什麼不可以直接run
闡述文章中已有,
Runnable
其實相對于一個Task,并不具有線程的概念,如果你直接去調用
Runnable
的
run
,其實就是相當于直接在主線程中執行了一個函數而已,并未開啟線程去執行,帶來的“災害”文章中有。
Java線程池中submit() 和 execute()方法有什麼差別?
submit()
可以擷取傳回值,
execute()
不行。
RUN()
和
Start()
方法差別是,
Start()
會重新建立一個線程去運作,而
RUN()
是隻是去單獨的調用。
可以用RUN()完成線程的同步
CompletableFuture,這個是JDK1.8裡的新特性,通過它怎麼實作多線程并發控制?
多線程實操
面試題,子線程10次子線程2執行20次與主線程100次來回循環執行50次
如何讓多個線程交替執行?
一種是基于
synchronized
和
wait/notify
,另外一種是基于
Lock
和
Condition(ReentrantLock)
.
- 循環内加鎖
- 判斷共享狀态變量,如果狀态值表示還沒輪到目前線程執行,則調用調用鎖對象的
方法wait
- 等待狀态變化的線程被喚醒,執行任務,然後改變狀态,然後調用鎖對象的
方法notify
使用者自己定義一個類,用這個類繼承
thread
類,實作
thread
的
run
方法,然後再使用使用者自己定義這個類建立一個對象,最後調用
thread
類的
start
方法激活線程。
VOLATILE
可見性,是指線程之間的可見性,一個線程修改的狀态對另一個線程是可見的。
- 記憶體屏障
- 記憶體可見
1.當寫一個
volatile
變量時,JMM會把該線程對應的本地記憶體中的變量強制重新整理到主記憶體中去;
2.這個寫操作會導緻其他線程中的緩存無效。
- 禁止指令重排序優化。
- 不能保證原子性
volatile對于單個的共享變量的讀/寫具有原子性,但是像num++這種複合操作,volatile無法保證其原子性,當然文中也提出了解決方案,就是使用并發包中的原子操作類,通過循環CAS地方式來保證num++操作的原子性
volatile
隻保證可見性 有序性,不保證原子性的原因,在于寄存器無法被
volatile
重新整理
- 你說的本地緩存,指的是線程工作記憶體,不是cpu的寄存器
- 線程工作記憶體,确實被設定為無效,下次直接從記憶體裡面取,但是寄存器在之前已經進去了,這是導緻
不能保證原子性的原因。如果volatile
也能把寄存器的值給設定為無效,那麼原子性就保證了volatile
LOCK字首指令篇(一):Java 之深入淺出解讀 volatile
原創|《菜鳥讀并發》java記憶體模型之volatile深入解讀
一文帶你了解Java中Lock的實作原理
Synchronized
volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻隻允許一條線程操作。
- 既可以修飾方法也可以修飾代碼塊
對于同步方法,JVM采用
ACC_SYNCHRONIZED
标記符來實作同步。 對于同步代碼塊。JVM采用
monitorenter
、
monitorexit
兩個指令來實作同步。
- 同步代碼塊使用
和monitorenter
兩個指令實作。可以把執行monitorexit
指令了解為加鎖,執行monitorenter
了解為釋放鎖。 每個對象維護着一個記錄着被鎖次數的計數器。未被鎖定的對象的該計數器為0,當一個線程獲得鎖(執行monitorexit
)後,該計數器自增變為 1 ,當同一個線程再次獲得該對象的鎖的時候,計數器再次自增。當同一個線程釋放鎖(執行monitorenter
指令)的時候,計數器再自減。當計數器為0的時候。鎖将被釋放,其他線程便可以獲得鎖。monitorexit
sychronized
是java中最基本同步互斥的手段,可以修飾代碼塊,方法,類.
在修飾代碼塊的時候需要一個
reference
對象作為鎖的對象.
在修飾方法的時候預設是目前對象作為鎖的對象.
在修飾類時候預設是目前類的
Class
對象作為鎖的對象.
synchronized
會在進入同步塊的前後分别形成
monitorenter
和
monitorexit
位元組碼指令.在執行
monitorenter
指令時會嘗試擷取對象的鎖,如果此沒對象沒有被鎖,或者此對象已經被目前線程鎖住,那麼鎖的計數器加一,每當
monitorexit
被鎖的對象的計數器減一.直到為0就釋放該對象的鎖.由此
synchronized
是可重入的,不會出現自己把自己鎖死.
sychronized
是java中最基本同步互斥的手段,可以修飾代碼塊,方法,類.
在修飾代碼塊的時候需要一個
reference
對象作為鎖的對象.
在修飾方法的時候預設是目前對象作為鎖的對象.
在修飾類時候預設是目前類的Class對象作為鎖的對象.
synchronized
會在進入同步塊的前後分别形成
monitorenter
和
monitorexit
位元組碼指令.在執行
monitorenter
指令時會嘗試擷取對象的鎖,如果此沒對象沒有被鎖,或者此對象已經被目前線程鎖住,那麼鎖的計數器加一,每當
monitorexit
被鎖的對象的計數器減一.直到為0就釋放該對象的鎖.由此
synchronized
是可重入的,不會出現自己把自己鎖死.
synchronized 和 ReentrantLock有什麼差別
除了
synchronized
的功能,多了三個進階功能.
等待可中斷,公平鎖,綁定多個
Condition
.
- 等待可中斷:在持有鎖的線程長時間不釋放鎖的時候,等待的線程可以選擇放棄等待. tryLock(long timeout, TimeUnit unit)
- 公平鎖:按照申請鎖的順序來一次獲得鎖稱為公平鎖.
的是非公平鎖,synchronized
可以通過構造函數實作公平鎖. new RenentrantLock(boolean fair)ReentrantLock
- 綁定多個
Condition
:
通過多次
可以獲得多個newCondition
對象,可以簡單的實作比較複雜的線程同步的功能.通過Condition
;await(),signal()
JAVA兩個字元串如何比較大小
compareTo() 的傳回值是int, 它是先比較對應字元的大小(ASCII碼順序)
Lock與synchronized有以下差別:
-
會自動釋放鎖,而synchronized
必須手動釋放鎖。Lock
-
可以讓等待鎖的線程響應中斷,而Lock
不會,線程會一直等待下去。synchronized
- 通過
可以知道線程有沒有拿到鎖,而Lock
不能。synchronized
-
能提高多個線程讀操作的效率。Lock
-
能鎖住類、方法和代碼塊,而synchronized
是塊範圍内的。Lock
volatile和synchronized的差別
-
比volatile
執行成本更低,因為它不會引起線程上下文的切換和排程synchronized
-
本質是在告訴volatile
目前變量在寄存器(工作記憶體)中的值是不确定的,需要從主存中讀取;jvm
則是鎖定目前變量,隻有目前線程可以通路該變量,其他線程被阻塞住。synchronized
-
隻能用來修飾變量,而volatile
可以用來修飾變量、方法、和類。synchronized
-
可以實作變量的可見性,禁止重排序和單次讀/寫的原子性;而volatile
則可以變量的可見性,禁止重排序和原子性。synchronized
-
不會造成線程的阻塞;volatile
可能會造成線程的阻塞。synchronized
-
标記的變量不會被編譯器優化;volatile
标記的變量可以被編譯器優化。synchronized
深入解讀synchronized和ReentrantLock
-
阻塞的線程狀态為synchronized
BLOCKED
-
阻塞的線程狀态為ReentrantLock
或者BLOCKED
WAITTING
深入解讀synchronized和ReentrantLock
synchronized 和 ReentrantLock有什麼差別
除了
synchronized
的功能,多了三個進階功能.
等待可中斷,公平鎖,綁定多個
Condition
.
- 等待可中斷:在持有鎖的線程長時間不釋放鎖的時候,等待的線程可以選擇放棄等待.
tryLock(long timeout, TimeUnit unit)
- 公平鎖:按照申請鎖的順序來一次獲得鎖稱為公平鎖.
的是非公平鎖,synchronized
可以通過構造函數實作公平鎖.ReentrantLock
new RenentrantLock(boolean fair)
- 綁定多個
Condition
:
通過多次
可以獲得多個newCondition
對象,可以簡單的實作比較複雜的線程同步的功能.通過Condition
await(),signal();
鎖更新
synchronized
鎖更新原理:在鎖對象的對象頭裡面有一個
threadid
字段,在第一次通路的時候
threadid
為空,
jvm
讓其持有偏向鎖,并将
threadid
設定為其線程
id
,再次進入的時候會先判斷
threadid
是否與其線程
id
一緻,如果一緻則可以直接使用此對象,如果不一緻,則更新偏向鎖為輕量級鎖,通過自旋循環一定次數來擷取鎖,執行一定次數之後,如果還沒有正常擷取到要使用的對象,此時就會把鎖從輕量級更新為重量級鎖,此過程就構成了
synchronized
鎖的更新。
鎖的更新的目的:鎖更新是為了減低了鎖帶來的性能消耗。在
Java 6
之後優化
synchronized
的實作方式,使用了偏向鎖更新為輕量級鎖再更新到重量級鎖的方式,進而減低了鎖帶來的性能消耗。
偏向鎖
把MARKWORD的threadID改成自己的threadID
在鎖競争不強烈的情況下,通常一個線程會多次擷取同一個鎖,為了減少擷取鎖的代價 引入了偏向鎖,會在
Java
對象頭中記錄擷取鎖的線程的
threadID
。
- 當線程發現對象頭的
存在時。判斷與目前線程是否是同一線程。threadID
- 如果是則不需要再次加、解鎖。
- 如果不是,則判斷
是否存活。不存活:設定為無鎖狀态,其他線程競争設定偏向鎖。存活:查找threadID
堆棧資訊判斷是否需要繼續持有鎖。需要持有則更新threadID
線程的鎖為輕量級鎖。不需要持有則撤銷鎖,設定為無鎖狀态等待其它線程競争。threadID
不是同一個對象就更新為輕量級鎖
因為偏向鎖的撤銷操作還是比較重的,導緻進入安全點,是以在競争比較激烈時,會影響性能,可以使用
-XX:-UseBiasedLocking=false
禁用偏向鎖。
輕量級鎖
當偏向鎖更新為輕量級鎖時,其它線程嘗試通過CAS方式設定對象頭來擷取鎖。
給MARKWORD一個指針,指向自己的lock record
- 會先在目前線程的棧幀中設定
,用于存儲目前對象頭中的Lock Record
的拷貝。mark word
- 複制
的内容到mark word
,并嘗試使用lock record
将cas
的指針指向mark word
lock record
- 如果替換成功,則擷取偏向鎖
- 替換不成功,則會自旋重試一定次數。
- 自旋一定次數或有新的線程來競争鎖時,輕量級鎖膨脹為重量級鎖。
重量級鎖
自旋是消耗CPU的,是以在自旋一段時間,或者一個線程在自旋時,又有新的線程來競争鎖,則輕量級鎖會膨脹為重量級鎖。重量級鎖,通過
monitor
實作,
monitor
底層實際是依賴作業系統的
mutex lock
(互斥鎖)實作。需要從使用者态,切換為核心态,成本比較高
CAS
CAS
可能遇到
ABA
問題,即記憶體中的值為
A
,變為
B
後,又變為了
A
,此時
A
為新值,不應該替換。可以采取:
A-1
,
B-2
,
A-3
的方式來避免這個問題
ABA問題是一種異常現象:如果在算法中的節點可以被循環使用,那麼在使用“比較并交換”指令時就可能出現這個問題(如果在沒有垃圾回收機制的環境 中)。在CAS操作中将判斷“V的值是否仍然為A?”,并且如果是的話就繼續執行更新操作。在大多數情況下,這種判斷是足夠的。然而,有時候還需要知道 “自從上次看到V的值為A以來,這個值是否發生了變化?”在某些算法中,如果V值首先由A程式設計B,在由B程式設計A,那麼仍然被認為發生了變化,并需要重新執 行算法中的某些步驟。
如果在算法中采用自己的方式來管理節點對象的記憶體,那麼可能出現
ABA
問題。在這種情況下,即使連結清單的頭結點仍然隻想之前觀察到的節點,那麼也不足 以說明連結清單的内容沒有發生變化。
如果通過垃圾回收器來管理連結清單節點仍然無法避免
ABA
問題,那麼還有一個相對簡單的解決方法:不是隻是更新某個引用的值, 而是更新兩個值,包含一個引用和一個版本号。即使這個值由
A
變成
B
,然後又變為
A
,版本号也将是不同的。
AtomicStampedReference
以 及
AtomicMarkableReference
支援在兩個變量上執行原子的條件更新。
AtomicStampedReference
将更新一個“對象 —-引用”二進制組,通過在引用上加上“版本号”,進而避免
ABA
問題。類似地,
AtomicMarkableReference
将更新一個“對象引用—- 布爾值”二進制組,在某些算法中将通過這種二進制組使節點儲存在連結清單中同時又将其标記為“已删除節點”。
參考
- Java synchronized 原理從開始到放棄
- 再有人問你synchronized是什麼,就把這篇文章發給他。
ReentrantLock
以對象的方式來操作對象鎖.相對于
sychronized
需要在
finally
中去釋放鎖,需要手動釋放。
ReentrantLock
裡面的
true
和
false
就是 公平鎖和非公平鎖的實作
ReentrantLock
重入鎖,是實作
Lock
接口的一個類,也是在實際程式設計中使用頻率很高的一個鎖,支援重入性,表示能夠對共享資源能夠重複加鎖,即目前線程擷取該鎖再次擷取不會被阻塞。在
java
關鍵字
synchronized
隐式支援重入性,
synchronized
通過擷取自增,釋放自減的方式實作重入。與此同時,
ReentrantLock
還支援公平鎖和非公平鎖兩種方式。
在
ReentrantLock
中
state
字段表示目前線程重入鎖的次數,當state為0時候,表示鎖是空閑的。
compareAndSetState(0, 1)
表示當
state
是
,直接更新為
1
,即空閑時候,直接
CAS
上鎖;線程如果更新成功了,
setExclusiveOwnerThread()
設定目前線程為鎖的占有
可重入鎖原理
重入鎖實作可重入性原理或機制是:每一個鎖關聯一個線程持有者和計數器,當計數器為 0 時表示該鎖沒有被任何線程持有,那麼任何線程都可能獲得該鎖而調用相應的方法;
當某一線程請求成功後,JVM會記下鎖的持有線程,并且将計數器置為 1;此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增;當線程退出同步代碼塊時,計數器會遞減,如果計數器為 0,則釋放該鎖
ReentrantLock
還是使用上面的例子,
ReentrantLock
的使用很簡單,使用
new ReentrantLock(boolean isFair)
來建立一個公平或者非公平鎖,使用.lock()方法加鎖。使用.unlock()方法,是以我們就從這三個方法入手,來簡單的看一下它是如何實作鎖的。
ReentrantLock
主要是用了一個巧妙的資料結構(帶頭尾指針的雙連結清單)和
CAS
加自旋以及使用
LockSupport
的
park
,
unpark
(類似
wait
,
notify
)來實作加鎖和解鎖。
CAS原理
AQS (AbstractQueuedSynchronizer)
由一個state和cas 阻塞隊列組成
什麼是AQS
AQS(AbstractQueuedSynchronizer)
,
AQS
是JDK下提供的一套用于實作基于FIFO等待隊列的阻塞鎖和相關的同步器的一個同步架構。這個抽象類被設計為作為一些可用原子
int
值來表示狀态的同步器的基類。如果你有看過類似
CountDownLatch
類的源碼實作,會發現其内部有一個繼承了
AbstractQueuedSynchronizer
的内部類
Sync
。可見
CountDownLatch
是基于
AQS
架構來實作的一個同步器.類似的同步器在
JUC
下還有不少。(eg.
Semaphore
)
AQS用法
如上所述,
AQS
管理一個關于狀态資訊的單一整數,該整數可以表現任何狀态。比如,
Semaphore
用它來表現剩餘的許可數,
ReentrantLock
用它來表現擁有它的線程已經請求了多少次鎖;
FutureTask
用它來表現任務的狀态(尚未開始、運作、完成和取消)
AQS AbstractQueuedSynchronizer
J.U.C
是基于
AQS
實作的,
AQS
是一個同步器,設計模式是模闆模式。
- 核心資料結構:雙向連結清單 +
(鎖狀态)state
- 底層操作:
CAS
Java多線程導論及延申我的部落格中有更多後端開發面試題,點我檢視!多線程導論VOLATILESynchronizedReentrantLockThreadLocal
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AQS
中的
int
類型的
state
值,這裡就是通過
CAS
(樂觀鎖)去修改
state
的值。
lock
的基本操作還是通過樂觀鎖來實作的。
擷取鎖通過
CAS
,那麼沒有擷取到鎖,等待擷取鎖是如何實作的?我們可以看一下
else
分支的邏輯,
acquire
方法:
-
:會嘗試再次通過tryAcquire
擷取一次鎖。CAS
-
:通過自旋addWaiter
,将目前線程加入上面鎖的雙向連結清單(等待隊列)中。CAS
-
:通過自旋,判斷目前隊列節點是否可以擷取鎖。acquireQueued
可以看到,當目前線程到頭部的時候,嘗試
CAS
更新鎖狀态,如果更新成功表示該等待線程擷取成功。從頭部移除。基本可以确認,釋放鎖就是對
AQS
中的狀态值
State
進行修改。同時更新下一個連結清單中的線程等待節點。
可以看到在整個實作過程中,
lock
大量使用
CAS
+自旋。是以根據
CAS
特性,
lock
建議使用在低鎖沖突的情況下。目前java1.6以後,官方對
synchronized
做了大量的鎖優化(偏向鎖、自旋、輕量級鎖)。是以在非必要的情況下,建議使用
synchronized
做同步操作。
AQS
定義兩種資源共享方式:
Exclusive(
獨占,隻有一個線程能執行,如
ReentrantLock
和
Share
(共享,多個線程可同時執行,如
Semaphore
/
CountDownLatch
)。
先大體說一下同步等待隊列的特點:先進先出。擷取鎖失敗的線程構造節點加入隊列尾部,阻塞自己,等待喚醒。執行完成的線程從頭部移出隊列,并喚醒後續節點的線程。頭結點是目前擷取到鎖的線程
公平鎖:
非公平鎖:
偏向鎖
鎖不存在多線程競争,并且應由一個線程多次獲得鎖
當線程通路同步塊時,會使用 CAS 将線程 ID 更新到鎖對象的 MarkWord 中,如果更新成功則獲得偏向鎖,并且之後每次進入這個對象鎖相關的同步塊時都不需要再次擷取鎖了。(檢查thread id)
輕量級鎖
當代碼進入同步塊時,如果同步對象為無鎖狀态時,目前線程會在棧幀中建立一個鎖記錄( LockRecord)區域,同時将鎖對象的對象頭中 MarkWord 拷貝到鎖記錄中,再嘗試使用 CAS 将 MarkWord 更新為指向鎖記錄的指針。
如果更新成功,目前線程就獲得了鎖。
如果更新失敗 JVM 會先檢查鎖對象的
MarkWord
是否指向目前線程的鎖記錄。
如果是則說明目前線程擁有鎖對象的鎖,可以直接進入同步塊。
不是則說明有其他線程搶占了鎖,如果存在多個線程同時競争一把鎖,輕量鎖就會膨脹為重量鎖。
輕量鎖能提升性能的原因是:認為大多數鎖在整個同步周期都不存在競争,是以使用 CAS 比使用互斥開銷更少。但如果鎖競争激烈,輕量鎖就不但有互斥的開銷,還有 CAS 的開銷,甚至比重量鎖更慢。
會自旋,否則進行鎖膨脹
重量級鎖
适應性自旋
在使用
CAS
時,如果操作失敗,
CAS
會自旋再次嘗試。由于自旋是需要消耗
CPU
資源的,是以如果長期自旋就白白浪費了
CPU
。 JDK1.6加入了适應性自旋:
如果某個鎖自旋很少成功獲得,那麼下一次就會減少自旋。
ABA問題(CAS的應用:AtomicStampedReference)
- 一個變量
;int state = 0
- 線程
讀取到A
;state = 0
- 線程
修改B
再改回state = 1
state = 0
;
此時線程 A 再讀取
,還是 ,但實際上已經state
已經被修改過。state
對于對改動敏感的變量操作,可以使用
AtomicStampedReference
,它會同時儲存時間戳以确認是否發生改動。
參考
- ReentrantLock原理從開始到放棄
ThreadLocal
Map的Key是弱引用類型(WeakReference),而Value是強引用類型,如果Key被回收,Value卻不會被回收。
ThreadLocal
和
Synchronized
都是為了解決多線程中相同變量的通路沖突問題,不同的點是
-
是通過線程等待,犧牲時間來解決通路沖突Synchronized
-
是通過每個線程單獨一份存儲空間,犧牲空間來解決沖突,并且相比于ThreadLocal
,Synchronized
具有線程隔離的效果,隻有線上程内才能擷取到對應的值,線程外則不能通路到想要的值。ThreadLocal
由于
Thread
包含變量
ThreadLocalMap
,是以
ThreadLocalMap
與
Thread
的生命周期是一樣長,如果都沒有手動删除對應key,都會導緻記憶體洩漏。
但是使用弱引用可以多一層保障:弱引用
ThreadLocal
不會記憶體洩漏,對應的
value
在下一次
ThreadLocalMap
調用
set()
,
get()
,
remove()
的時候會被清除。
是以,
ThreadLocal
記憶體洩漏的根源是:由于
ThreadLocalMap
的生命周期跟
Thread
一樣長,如果沒有手動删除對應
key
就會導緻記憶體洩漏,而不是因為弱引用。
參考
- ThreadLocal記憶體洩露的原因
實作線程安全的方式
- 加鎖 利用Synchronized或者ReenTrantLock來對不安全對象進行加鎖,來實作線程執行的串行化,進而保證多線程同時操作對象的安全性,一個是文法層面的互斥鎖,一個是API層面的互斥鎖.
- 非阻塞同步來實作線程安全。原理就是:通俗點講,就是先進性操作,如果沒有其他線程争用共享資料,那操作就成功了;如果共享資料有争用,産生沖突,那就再采取其他措施(最常見的措施就是不斷地重試,直到成功為止)。這種方法需要硬體的支援,因為我們需要操作和沖突檢測這兩個步驟具備原子性。通常這種指令包括CAS SC,FAI TAS等
- 線程本地化,一種無同步的方案,就是利用Threadlocal來為每一個線程創造一個共享變量的副本來(副本之間是無關的)避免幾個線程同時操作一個對象時發生線程安全問題。
阻塞隊列
-
:基于數組實作的一個阻塞隊列,在建立ArrayBlockingQueue
對象時必須制定容量大小。并且可以指定公平性與非公平性,預設情況下為非公平的,即不保證等待時間最長的隊列最優先能夠通路隊列。ArrayBlockingQueue
-
:基于連結清單實作的一個阻塞隊列,在建立LinkedBlockingQueue
對象時如果不指定容量大小,則預設大小為LinkedBlockingQueue
。Integer.MAX_VALUE
-
:以上2種隊列都是先進先出隊列,而PriorityBlockingQueue卻不是,它會按照元素的優先級對元素進行排序,按照優先級順序出隊,每次出隊的元素都是優先級最高的元素。注意,此阻塞隊列為無界阻塞隊列,即容量沒有上限(通過源碼就可以知道,它沒有容器滿的信号标志),前面2種都是有界隊列。PriorityBlockingQueue
-
:基于DelayQueue
,一種延時阻塞隊列,DelayQueue中的元素隻有當其指定的延遲時間到了,才能夠從隊列中擷取到該元素。PriorityQueue
也是一個無界隊列,是以往隊列中插入資料的操作(生産者)永遠不會被阻塞,而隻有擷取資料的操作(消費者)才會被阻塞。DelayQueue