轉(zhuǎn)自(侵刪):http://blog.csdn.net/a314773862/article/details/54095819
1、自旋鎖
自旋鎖可以使線程在沒有取得鎖的時(shí)候,不被掛起,而轉(zhuǎn)去執(zhí)行一個(gè)空循環(huán),(即所謂的自旋,就是自己執(zhí)行空循環(huán)),若在若干個(gè)空循環(huán)后,線程如果可以獲得鎖,則繼續(xù)執(zhí)行。若線程依然不能獲得鎖,才會被掛起。
使用自旋鎖后,線程被掛起的幾率相對減少,線程執(zhí)行的連貫性相對加強(qiáng)。因此,對于那些鎖競爭不是很激烈,鎖占用時(shí)間很短的并發(fā)線程,具有一定的積極意義,但對于鎖競爭激烈,單線程鎖占用很長時(shí)間的并發(fā)程序,自旋鎖在自旋等待后,往往毅然無法獲得對應(yīng)的鎖,不僅僅白白浪費(fèi)了CPU時(shí)間,最終還是免不了被掛起的操作 ,反而浪費(fèi)了系統(tǒng)的資源。
在JDK1.6中,Java虛擬機(jī)提供-XX:+UseSpinning參數(shù)來開啟自旋鎖,使用-XX:PreBlockSpin參數(shù)來設(shè)置自旋鎖等待的次數(shù)。
在JDK1.7開始,自旋鎖的參數(shù)被取消,虛擬機(jī)不再支持由用戶配置自旋鎖,自旋鎖總是會執(zhí)行,自旋鎖次數(shù)也由虛擬機(jī)自動調(diào)整。
可能引起的問題:
1.過多占據(jù)CPU時(shí)間:如果鎖的當(dāng)前持有者長時(shí)間不釋放該鎖,那么等待者將長時(shí)間的占據(jù)cpu時(shí)間片,導(dǎo)致CPU資源的浪費(fèi),因此可以設(shè)定一個(gè)時(shí)間,當(dāng)鎖持有者超過這個(gè)時(shí)間不釋放鎖時(shí),等待者會放棄CPU時(shí)間片阻塞;
2.死鎖問題:試想一下,有一個(gè)線程連續(xù)兩次試圖獲得自旋鎖(比如在遞歸程序中),第一次這個(gè)線程獲得了該鎖,當(dāng)?shù)诙卧噲D加鎖的時(shí)候,檢測到鎖已被占用(其實(shí)是被自己占用),那么這時(shí),線程會一直等待自己釋放該鎖,而不能繼續(xù)執(zhí)行,這樣就引起了死鎖。因此遞歸程序使用自旋鎖應(yīng)該遵循以下原則:遞歸程序決不能在持有自旋鎖時(shí)調(diào)用它自己,也決不能在遞歸調(diào)用時(shí)試圖獲得相同的自旋鎖。
2、阻塞鎖
讓線程進(jìn)入阻塞狀態(tài)進(jìn)行等待,當(dāng)獲得相應(yīng)的信號(喚醒,時(shí)間) 時(shí),才可以進(jìn)入線程的準(zhǔn)備就緒狀態(tài),準(zhǔn)備就緒狀態(tài)的所有線程,通過競爭,進(jìn)入運(yùn)行狀態(tài)。。
JAVA中,能夠進(jìn)入\退出、阻塞狀態(tài)或包含阻塞鎖的方法有 ,synchronized 關(guān)鍵字(其中的重量鎖),ReentrantLock,Object.wait()\notify()
3、可重入鎖
可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數(shù)獲得鎖之后 ,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼,但不受影響。
在JAVA環(huán)境下 ReentrantLock 和synchronized 都是 可重入鎖
下面是使用實(shí)例
1. public class Test implements Runnable{
3. public synchronized void get(){
4. System.out.println(Thread.currentThread().getId());
5. set();
6. }
8. public synchronized void set(){
9. System.out.println(Thread.currentThread().getId());
10. }
12. @Override
13. public void run() {
14. get();
15. }
16. public static void main(String[] args) {
17. Test ss=new Test();
18. new Thread(ss).start();
19. new Thread(ss).start();
20. new Thread(ss).start();
21. }
22. }
24. public class Test implements Runnable {
25. ReentrantLock lock = new ReentrantLock();
27. public void get() {
28. lock.lock();
29. System.out.println(Thread.currentThread().getId());
30. set();
31. lock.unlock();
32. }
34. public void set() {
35. lock.lock();
36. System.out.println(Thread.currentThread().getId());
37. lock.unlock();
38. }
40. @Override
41. public void run() {
42. get();
43. }
45. public static void main(String[] args) {
46. Test ss = new Test();
47. new Thread(ss).start();
48. new Thread(ss).start();
49. new Thread(ss).start();
50. }
51. }
public class Test implements Runnable{
public synchronized void get(){
System.out.println(Thread.currentThread().getId());
set();
}
public synchronized void set(){
System.out.println(Thread.currentThread().getId());
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss=new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
public class Test implements Runnable {
ReentrantLock lock = new ReentrantLock();
public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
兩個(gè)例子最后的結(jié)果都是正確的,即 同一個(gè)線程id被連續(xù)輸出兩次。
結(jié)果如下:
Threadid: 8
Threadid: 8
Threadid: 10
Threadid: 10
Threadid: 9
Threadid: 9
可重入鎖最大的作用是避免死鎖
我們以自旋鎖作為例子,
1. public class SpinLock {
2. private AtomicReference<Thread> owner =new AtomicReference<>();
3. public void lock(){
4. Thread current = Thread.currentThread();
5. while(!owner.compareAndSet(null, current)){
6. }
7. }
8. public void unlock (){
9. Thread current = Thread.currentThread();
10. owner.compareAndSet(current, null);
11. }
12. }
public class SpinLock {
private AtomicReference<Thread> owner =new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
while(!owner.compareAndSet(null, current)){
}
}
public void unlock (){
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
對于自旋鎖來說,
1、若有同一線程兩調(diào)用lock() ,會導(dǎo)致第二次調(diào)用lock位置進(jìn)行自旋,產(chǎn)生了死鎖
說明這個(gè)鎖并不是可重入的。(在lock函數(shù)內(nèi),應(yīng)驗(yàn)證線程是否為已經(jīng)獲得鎖的線程)
2、若1問題已經(jīng)解決,當(dāng)unlock()第一次調(diào)用時(shí),就已經(jīng)將鎖釋放了。實(shí)際上不應(yīng)釋放鎖。
(采用計(jì)數(shù)次進(jìn)行統(tǒng)計(jì))
修改之后,如下:
1. public class SpinLock1 {
2. private AtomicReference<Thread> owner =new AtomicReference<>();
3. private int count =0;
4. public void lock(){
5. Thread current = Thread.currentThread();
6. if(current==owner.get()) {
7. count++;
8. return ;
9. }
10. while(!owner.compareAndSet(null, current)){
11. }
12. }
13. public void unlock (){
14. Thread current = Thread.currentThread();
15. if(current==owner.get()){
16. if(count!=0){
17. count--;
18. }else{
19. owner.compareAndSet(current, null);
20. }
21. }
22. }
23. }
public class SpinLock1 {
private AtomicReference<Thread> owner =new AtomicReference<>();
private int count =0;
public void lock(){
Thread current = Thread.currentThread();
if(current==owner.get()) {
count++;
return ;
}
while(!owner.compareAndSet(null, current)){
}
}
public void unlock (){
Thread current = Thread.currentThread();
if(current==owner.get()){
if(count!=0){
count--;
}else{
owner.compareAndSet(current, null);
}
}
}
}
該自旋鎖即為可重入鎖。
4 悲觀鎖和樂觀鎖
悲觀鎖(Pessimistic Lock), 顧名思義就是很悲觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會修改,所以每次在拿數(shù)據(jù)的時(shí)候都會上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會block直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。獨(dú)占鎖是悲觀鎖的一種實(shí)現(xiàn)
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會修改,所以不會上鎖,但是在更新的時(shí)候會判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號等機(jī)制。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫如果提供類似于write_condition機(jī)制的其實(shí)都是提供的樂觀鎖。使用CAS來保證,保證這個(gè)操作的原子性
兩種鎖各有優(yōu)缺點(diǎn),不可認(rèn)為一種好于另一種,像樂觀鎖適用于寫比較少的情況下,即沖突真的很少發(fā)生的時(shí)候,這樣可以省去了鎖的開銷,加大了系統(tǒng)的整個(gè)吞吐量。但如果經(jīng)常產(chǎn)生沖突,上層應(yīng)用會不斷的進(jìn)行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
參考:http://www.cnblogs.com/softidea/p/5309312.html
http://blog.csdn.net/hongchangfirst/article/details/26004335
5 輪詢鎖和定時(shí)鎖
由tryLock實(shí)現(xiàn),與無條件獲取鎖模式相比,它們具有更完善的錯(cuò)誤恢復(fù)機(jī)制??杀苊馑梨i的發(fā)生:
boolean tryLock():僅在調(diào)用時(shí)鎖為空閑狀態(tài)才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值 true。如果鎖不可用,則此方法將立即返回值 false。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException:
如果鎖在給定的等待時(shí)間內(nèi)空閑,并且當(dāng)前線程未被中斷,則獲取鎖。
如果鎖可用,則此方法將立即返回值 true。如果鎖不可用,出于線程調(diào)度目的,將禁用當(dāng)前線程,并且在發(fā)生以下三種情況之一前,該線程將一直處于休眠狀態(tài):
鎖由當(dāng)前線程獲得;或者
其他某個(gè)線程中斷當(dāng)前線程,并且支持對鎖獲取的中斷;或者
已超過指定的等待時(shí)間
如果獲得了鎖,則返回值 true。
如果當(dāng)前線程:
在進(jìn)入此方法時(shí)已經(jīng)設(shè)置了該線程的中斷狀態(tài);或者
在獲取鎖時(shí)被中斷,并且支持對鎖獲取的中斷,
則將拋出 InterruptedException,并會清除當(dāng)前線程的已中斷狀態(tài)。
如果超過了指定的等待時(shí)間,則將返回值 false。如果 time 小于等于 0,該方法將完全不等待。
6 顯示鎖和內(nèi)置鎖
顯示鎖用Lock來定義、內(nèi)置鎖用syschronized。
內(nèi)置鎖:每個(gè)java對象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖,這些鎖成為內(nèi)置鎖。線程進(jìn)入同步代碼塊或方法的時(shí)候會自動獲得該鎖,在退出同步代碼塊或方法時(shí)會釋放該鎖。獲得內(nèi)置鎖的唯一途徑就是進(jìn)入這個(gè)鎖的保護(hù)的同步代碼塊或方法。
內(nèi)置鎖是互斥鎖。
7 讀-寫鎖
Lock接口以及對象,使用它,很優(yōu)雅的控制了競爭資源的安全訪問,但是這種鎖不區(qū)分讀寫,稱這種鎖為普通鎖。為了提高性能,Java提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,如果沒有寫鎖的情況下,讀是無阻塞的,在一定程度上提高了程序的執(zhí)行效率。
Java中讀寫鎖有個(gè)接口java.util.concurrent.locks.ReadWriteLock,也有具體的實(shí)現(xiàn)ReentrantReadWriteLock,詳細(xì)的API可以查看JavaAPI文檔。
ReentrantReadWriteLock 和 ReentrantLock 不是繼承關(guān)系,但都是基于 AbstractQueuedSynchronizer 來實(shí)現(xiàn)。
lock方法 是基于CAS 來實(shí)現(xiàn)的
ReadWriteLock中暴露了兩個(gè)Lock對象:
在讀寫鎖的加鎖策略中,允許多個(gè)讀操作同時(shí)進(jìn)行,但每次只允許一個(gè)寫操作。讀寫鎖是一種性能優(yōu)化的策略。
RentrantReadWriteLock在構(gòu)造時(shí)也可以選擇是一個(gè)非公平的鎖(默認(rèn))還是公平的鎖。
8 對象鎖和類鎖
java的對象鎖和類鎖在鎖的概念上基本上和內(nèi)置鎖是一致的,但是,兩個(gè)鎖實(shí)際是有很大的區(qū)別的,對象鎖是用于對象實(shí)例方法,或者一個(gè)對象實(shí)例上的,類鎖是用于類的靜態(tài)方法或者一個(gè)類的class對象上的。
類的對象實(shí)例可以有很多個(gè),但是每個(gè)類只有一個(gè)class對象,所以不同對象實(shí)例的對象鎖是互不干擾的,但是每個(gè)類只有一個(gè)類鎖。但是有一點(diǎn)必須注意的是,其實(shí)類鎖只是一個(gè)概念上的東西,并不是真實(shí)存在的,它只是用來幫助我們理解鎖定實(shí)例方法和靜態(tài)方法的區(qū)別的.
synchronized只是一個(gè)內(nèi)置鎖的加鎖機(jī)制,當(dāng)某個(gè)方法加上synchronized關(guān)鍵字后,就表明要獲得該內(nèi)置鎖才能執(zhí)行,并不能阻止其他線程訪問不需要獲得該內(nèi)置鎖的方法。
調(diào)用對象wait()方法時(shí),會釋放持有的對象鎖,以便于調(diào)用notify方法使用。notify()調(diào)用之后,會等到notify所在的線程執(zhí)行完之后再釋放鎖
9:鎖粗化(Lock Coarsening):
鎖粗化的概念應(yīng)該比較好理解,就是將多次連接在一起的加鎖、解鎖操作合并為一次,將多個(gè)連續(xù)的鎖擴(kuò)展成一個(gè)范圍更大的鎖。舉個(gè)例子:
1. 1 package com.paddx.test.string;
2. 2
3. 3 public class StringBufferTest {
4. 4 StringBuffer stringBuffer = new StringBuffer();
5. 5
6. 6 public void append(){
7. 7 stringBuffer.append("a");
8. 8 stringBuffer.append("b");
9. 9 stringBuffer.append("c");
10. 10 }
11. 11 }
1 package com.paddx.test.string;
2
3 public class StringBufferTest {
4 StringBuffer stringBuffer = new StringBuffer();
5
6 public void append(){
7 stringBuffer.append("a");
8 stringBuffer.append("b");
9 stringBuffer.append("c");
10 }
11 }
這里每次調(diào)用stringBuffer.append方法都需要加鎖和解鎖,如果虛擬機(jī)檢測到有一系列連串的對同一個(gè)對象加鎖和解鎖操作,就會將其合并成一次范圍更大的加鎖和解鎖操作,即在第一次append方法時(shí)進(jìn)行加鎖,最后一次append方法結(jié)束后進(jìn)行解鎖。
10 互斥鎖
互斥鎖, 指的是一次最多只能有一個(gè)線程持有的鎖。如Java的Lock
15 無鎖狀態(tài)-》偏向鎖-》輕量級鎖-》重量級鎖。鎖膨脹
鎖的狀態(tài)總共有四種:無鎖狀態(tài)、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現(xiàn)鎖的降級)。JDK 1.6中默認(rèn)是開啟偏向鎖和輕量級鎖的,
鎖膨脹:從輕量鎖膨脹到重量級鎖是在輕量級鎖解鎖過程發(fā)生的。
重量級鎖:Synchronized是通過對象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來實(shí)現(xiàn)的。但是監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的Mutex Lock來實(shí)現(xiàn)的。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換這就需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時(shí)間,這就是為什么Synchronized效率低的原因。因此,這種依賴于操作系統(tǒng)Mutex Lock所實(shí)現(xiàn)的鎖我們稱之為“重量級鎖”。
輕量級鎖:“輕量級”是相對于使用操作系統(tǒng)互斥量來實(shí)現(xiàn)的傳統(tǒng)鎖而言的。但是,首先需要強(qiáng)調(diào)一點(diǎn)的是,輕量級鎖并不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統(tǒng)的重量級鎖使用產(chǎn)生的性能消耗。在解釋輕量級鎖的執(zhí)行過程之前,先明白一點(diǎn),輕量級鎖所適應(yīng)的場景是線程交替執(zhí)行同步塊的情況,如果存在同一時(shí)間訪問同一鎖的情況,就會導(dǎo)致輕量級鎖膨脹為重量級鎖。
偏向鎖: 引入偏向鎖是為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執(zhí)行路徑,因?yàn)檩p量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時(shí)候依賴一次CAS原子指令(由于一旦出現(xiàn)多線程競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小于節(jié)省下來的CAS原子指令的性能消耗)。上面說過,輕量級鎖是為了在線程交替執(zhí)行同步塊時(shí)提高性能,而偏向鎖則是在只有一個(gè)線程執(zhí)行同步塊時(shí)進(jìn)一步提高性能。
無鎖狀態(tài):在代碼進(jìn)入同步塊的時(shí)候,如果同步對象鎖狀態(tài)為無鎖狀態(tài)。
重量級鎖、輕量級鎖和偏向鎖之間轉(zhuǎn)換:
11 鎖消除(Lock Elimination):鎖消除即刪除不必要的加鎖操作。根據(jù)代碼逃逸技術(shù),如果判斷到一段代碼中,堆上的數(shù)據(jù)不會逃逸出當(dāng)前線程,那么可以認(rèn)為這段代碼是線程安全的,不必要加鎖??聪旅孢@段程序:
3. public class SynchronizedTest02 {
5. public static void main(String[] args) {
6. SynchronizedTest02 test02 = new SynchronizedTest02();
7. //啟動預(yù)熱
8. for (int i = 0; i < 10000; i++) {
9. i++;
10. }
11. long start = System.currentTimeMillis();
12. for (int i = 0; i < 100000000; i++) {
13. test02.append("abc", "def");
14. }
15. System.out.println("Time=" + (System.currentTimeMillis() - start));
16. }
18. public void append(String str1, String str2) {
19. StringBuffer sb = new StringBuffer();
20. sb.append(str1).append(str2);
21. }
22. }
public class SynchronizedTest02 {
public static void main(String[] args) {
SynchronizedTest02 test02 = new SynchronizedTest02();
//啟動預(yù)熱
for (int i = 0; i < 10000; i++) {
i++;
}
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
test02.append("abc", "def");
}
System.out.println("Time=" + (System.currentTimeMillis() - start));
}
public void append(String str1, String str2) {
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}
}
雖然StringBuffer的append是一個(gè)同步方法,但是這段程序中的StringBuffer屬于一個(gè)局部變量,并且不會從該方法中逃逸出去,所以其實(shí)這過程是線程安全的,可以將鎖消除。下面是我本地執(zhí)行的結(jié)果
12、信號量
線程同步工具:Semaphore
http://ifeve.com/java_lock_see/
http://www.cnblogs.com/paddix/p/5405678.html
http://www.cnblogs.com/softidea/p/5530761.html
</article>
</main>