線程安全問題的產(chǎn)生前提是多個(gè)線程并發(fā)訪問共享數(shù)據(jù)。
將多個(gè)線程對(duì)共享數(shù)據(jù)的并發(fā)訪問轉(zhuǎn)換為串行訪問,即一個(gè)共享數(shù)據(jù)一次只能被一個(gè)線程訪問.鎖就是復(fù)用這種思路來保障線程安全的。
鎖(Lock)可以理解為對(duì)共享數(shù)據(jù)進(jìn)行保護(hù)的一個(gè)許可證. 對(duì)于同一個(gè)許可證保護(hù)的共享數(shù)據(jù)來說,任何線程想要訪問這些共享數(shù)據(jù)必須先持有該許可證. 一個(gè)線程只有在持有許可證的情況下才能對(duì)這些共享數(shù)據(jù)進(jìn)行訪問; 并且一個(gè)許可證一次只能被一個(gè)線程持有; 許可證線程在結(jié)束對(duì)共享數(shù)據(jù)的訪問后必須釋放其持有的許可證。
一線程在訪問共享數(shù)據(jù)前必須先獲得鎖; 獲得鎖的線程稱為鎖的持有線程; 一個(gè)鎖一次只能被一個(gè)線程持有. 鎖的持有線程在獲得鎖之后 和釋放鎖之前這段時(shí)間所執(zhí)行的代碼稱為臨界區(qū)(Critical Section)。
鎖具有排他性(Exclusive), 即一個(gè)鎖一次只能被一個(gè)線程持有.這種鎖稱為排它鎖或互斥鎖(Mutex)。
JVM把鎖分為內(nèi)部鎖和顯示鎖兩種. 內(nèi)部鎖通過synchronized關(guān)鍵字實(shí)現(xiàn); 顯示鎖通過java.concurrent.locks.Lock接口的實(shí)現(xiàn)類實(shí)現(xiàn)的。
鎖可以實(shí)現(xiàn)對(duì)共享數(shù)據(jù)的安全訪問. 保障線程的原子性,可見性與有序性。
鎖是通過互斥保障原子性. 一個(gè)鎖只能被一個(gè)線程持有, 這就保證臨界區(qū)的代碼一次只能被一個(gè)線程執(zhí)行.使得臨界區(qū)代碼所執(zhí)行的操作自然而然的具有不可分割的特性,即具備了原子性。
可見性的保障是通過寫線程沖刷處理器的緩存和讀線程刷新處理器緩存這兩個(gè) 動(dòng)作實(shí)現(xiàn)的. 在java平臺(tái)中,鎖的獲得隱含著刷新處理器緩存的動(dòng)作, 鎖的釋放隱含著沖刷處理器緩存的動(dòng)作。
鎖能夠保障有序性.寫線程在臨界區(qū)所執(zhí)行的在讀線程所執(zhí)行的臨界區(qū)看來像是完全按照源碼順序執(zhí)行的。
注意:
使用鎖保障線程的安全性,必須滿足以下條件:
● 這些線程在訪問共享數(shù)據(jù)時(shí)必須使用同一個(gè)鎖。
● 即使是讀取共享數(shù)據(jù)的線程也需要使用同步鎖。
可重入性(Reentrancy)描述這樣一個(gè)問題: 一個(gè)線程持有該鎖的時(shí)候能再次(多次)申請(qǐng)?jiān)撴i。
void methodA(){
申請(qǐng)a鎖
methodB();
釋放a鎖
}
void methodB(){
申請(qǐng)a鎖
....
釋放a鎖
}
如果一個(gè)線程持有一個(gè)鎖的時(shí)候還能夠繼續(xù)成功申請(qǐng)?jiān)撴i,稱該鎖是可重入的, 否則就稱該鎖為不可重入的。
Java平臺(tái)中內(nèi)部鎖屬于非公平鎖, 顯示Lock鎖既支持公平鎖又支持非公平鎖。
一個(gè)鎖可以保護(hù)的共享數(shù)據(jù)的數(shù)量大小稱為鎖的粒度。
鎖保護(hù)共享數(shù)據(jù)量大,稱該鎖的粒度粗, 否則就稱該鎖的粒度細(xì)。
鎖的粒度過粗會(huì)導(dǎo)致線程在申請(qǐng)鎖時(shí)會(huì)進(jìn)行不必要的等待.鎖的粒度過細(xì)會(huì)增加鎖調(diào)度的開銷。