轉自(侵刪):http://blog.csdn.net/a314773862/article/details/54095819
1、自旋鎖
自旋鎖可以使線程在沒有取得鎖的時候,不被掛起,而轉去執行一個空循環,(即所謂的自旋,就是自己執行空循環),若在若干個空循環后,線程如果可以獲得鎖,則繼續執行。若線程依然不能獲得鎖,才會被掛起。
使用自旋鎖后,線程被掛起的幾率相對減少,線程執行的連貫性相對加強。因此,對于那些鎖競爭不是很激烈,鎖占用時間很短的并發線程,具有一定的積極意義,但對于鎖競爭激烈,單線程鎖占用很長時間的并發程序,自旋鎖在自旋等待后,往往毅然無法獲得對應的鎖,不僅僅白白浪費了CPU時間,最終還是免不了被掛起的操作 ,反而浪費了系統的資源。
在JDK1.6中,Java虛擬機提供-XX:+UseSpinning參數來開啟自旋鎖,使用-XX:PreBlockSpin參數來設置自旋鎖等待的次數。
在JDK1.7開始,自旋鎖的參數被取消,虛擬機不再支持由用戶配置自旋鎖,自旋鎖總是會執行,自旋鎖次數也由虛擬機自動調整。
可能引起的問題:
1.過多占據CPU時間:如果鎖的當前持有者長時間不釋放該鎖,那么等待者將長時間的占據cpu時間片,導致CPU資源的浪費,因此可以設定一個時間,當鎖持有者超過這個時間不釋放鎖時,等待者會放棄CPU時間片阻塞;
2.死鎖問題:試想一下,有一個線程連續兩次試圖獲得自旋鎖(比如在遞歸程序中),第一次這個線程獲得了該鎖,當第二次試圖加鎖的時候,檢測到鎖已被占用(其實是被自己占用),那么這時,線程會一直等待自己釋放該鎖,而不能繼續執行,這樣就引起了死鎖。因此遞歸程序使用自旋鎖應該遵循以下原則:遞歸程序決不能在持有自旋鎖時調用它自己,也決不能在遞歸調用時試圖獲得相同的自旋鎖。
2、阻塞鎖
讓線程進入阻塞狀態進行等待,當獲得相應的信號(喚醒,時間) 時,才可以進入線程的準備就緒狀態,準備就緒狀態的所有線程,通過競爭,進入運行狀態。。
JAVA中,能夠進入\退出、阻塞狀態或包含阻塞鎖的方法有 ,synchronized 關鍵字(其中的重量鎖),ReentrantLock,Object.wait()\notify()
3、可重入鎖
可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之后 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。
在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖
下面是使用實例
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();
}
}
兩個例子最后的結果都是正確的,即 同一個線程id被連續輸出兩次。
結果如下:
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、若有同一線程兩調用lock() ,會導致第二次調用lock位置進行自旋,產生了死鎖
說明這個鎖并不是可重入的。(在lock函數內,應驗證線程是否為已經獲得鎖的線程)
2、若1問題已經解決,當unlock()第一次調用時,就已經將鎖釋放了。實際上不應釋放鎖。
(采用計數次進行統計)
修改之后,如下:
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), 顧名思義就是很悲觀,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。獨占鎖是悲觀鎖的一種實現
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似于write_condition機制的其實都是提供的樂觀鎖。使用CAS來保證,保證這個操作的原子性
兩種鎖各有優缺點,不可認為一種好于另一種,像樂觀鎖適用于寫比較少的情況下,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生沖突,上層應用會不斷的進行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
參考:http://www.cnblogs.com/softidea/p/5309312.html
http://blog.csdn.net/hongchangfirst/article/details/26004335
5 輪詢鎖和定時鎖
由tryLock實現,與無條件獲取鎖模式相比,它們具有更完善的錯誤恢復機制。可避免死鎖的發生:
boolean tryLock():僅在調用時鎖為空閑狀態才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值 true。如果鎖不可用,則此方法將立即返回值 false。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException:
如果鎖在給定的等待時間內空閑,并且當前線程未被中斷,則獲取鎖。
如果鎖可用,則此方法將立即返回值 true。如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在發生以下三種情況之一前,該線程將一直處于休眠狀態:
鎖由當前線程獲得;或者
其他某個線程中斷當前線程,并且支持對鎖獲取的中斷;或者
已超過指定的等待時間
如果獲得了鎖,則返回值 true。
如果當前線程:
在進入此方法時已經設置了該線程的中斷狀態;或者
在獲取鎖時被中斷,并且支持對鎖獲取的中斷,
則將拋出 InterruptedException,并會清除當前線程的已中斷狀態。
如果超過了指定的等待時間,則將返回值 false。如果 time 小于等于 0,該方法將完全不等待。
6 顯示鎖和內置鎖
顯示鎖用Lock來定義、內置鎖用syschronized。
內置鎖:每個java對象都可以用做一個實現同步的鎖,這些鎖成為內置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。
內置鎖是互斥鎖。
7 讀-寫鎖
Lock接口以及對象,使用它,很優雅的控制了競爭資源的安全訪問,但是這種鎖不區分讀寫,稱這種鎖為普通鎖。為了提高性能,Java提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,如果沒有寫鎖的情況下,讀是無阻塞的,在一定程度上提高了程序的執行效率。
Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現ReentrantReadWriteLock,詳細的API可以查看JavaAPI文檔。
ReentrantReadWriteLock 和 ReentrantLock 不是繼承關系,但都是基于 AbstractQueuedSynchronizer 來實現。
lock方法 是基于CAS 來實現的
ReadWriteLock中暴露了兩個Lock對象:
在讀寫鎖的加鎖策略中,允許多個讀操作同時進行,但每次只允許一個寫操作。讀寫鎖是一種性能優化的策略。
RentrantReadWriteLock在構造時也可以選擇是一個非公平的鎖(默認)還是公平的鎖。
8 對象鎖和類鎖
java的對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是,兩個鎖實際是有很大的區別的,對象鎖是用于對象實例方法,或者一個對象實例上的,類鎖是用于類的靜態方法或者一個類的class對象上的。
類的對象實例可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,并不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的.
synchronized只是一個內置鎖的加鎖機制,當某個方法加上synchronized關鍵字后,就表明要獲得該內置鎖才能執行,并不能阻止其他線程訪問不需要獲得該內置鎖的方法。
調用對象wait()方法時,會釋放持有的對象鎖,以便于調用notify方法使用。notify()調用之后,會等到notify所在的線程執行完之后再釋放鎖
9:鎖粗化(Lock Coarsening):
鎖粗化的概念應該比較好理解,就是將多次連接在一起的加鎖、解鎖操作合并為一次,將多個連續的鎖擴展成一個范圍更大的鎖。舉個例子:
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 }
這里每次調用stringBuffer.append方法都需要加鎖和解鎖,如果虛擬機檢測到有一系列連串的對同一個對象加鎖和解鎖操作,就會將其合并成一次范圍更大的加鎖和解鎖操作,即在第一次append方法時進行加鎖,最后一次append方法結束后進行解鎖。
10 互斥鎖
互斥鎖, 指的是一次最多只能有一個線程持有的鎖。如Java的Lock
15 無鎖狀態-》偏向鎖-》輕量級鎖-》重量級鎖。鎖膨脹
鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。JDK 1.6中默認是開啟偏向鎖和輕量級鎖的,
鎖膨脹:從輕量鎖膨脹到重量級鎖是在輕量級鎖解鎖過程發生的。
重量級鎖:Synchronized是通過對象內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴于底層的操作系統的Mutex Lock來實現的。而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什么Synchronized效率低的原因。因此,這種依賴于操作系統Mutex Lock所實現的鎖我們稱之為“重量級鎖”。
輕量級鎖:“輕量級”是相對于使用操作系統互斥量來實現的傳統鎖而言的。但是,首先需要強調一點的是,輕量級鎖并不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗。在解釋輕量級鎖的執行過程之前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。
偏向鎖: 引入偏向鎖是為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令(由于一旦出現多線程競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小于節省下來的CAS原子指令的性能消耗)。上面說過,輕量級鎖是為了在線程交替執行同步塊時提高性能,而偏向鎖則是在只有一個線程執行同步塊時進一步提高性能。
無鎖狀態:在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態。
重量級鎖、輕量級鎖和偏向鎖之間轉換:
11 鎖消除(Lock Elimination):鎖消除即刪除不必要的加鎖操作。根據代碼逃逸技術,如果判斷到一段代碼中,堆上的數據不會逃逸出當前線程,那么可以認為這段代碼是線程安全的,不必要加鎖。看下面這段程序:
3. public class SynchronizedTest02 {
5. public static void main(String[] args) {
6. SynchronizedTest02 test02 = new SynchronizedTest02();
7. //啟動預熱
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();
//啟動預熱
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是一個同步方法,但是這段程序中的StringBuffer屬于一個局部變量,并且不會從該方法中逃逸出去,所以其實這過程是線程安全的,可以將鎖消除。下面是我本地執行的結果
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>