自旋鎖


原創轉載請注明出處:https://www.cnblogs.com/agilestyle/p/11395994.html

 

CAS

CAS算法是樂觀鎖的一種實現方式,CAS算法中又涉及到自旋鎖。

CAS是英文單詞Compare and Swap(比較並交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。

CAS算法涉及到三個操作數

1.需要讀寫的內存值 V
2.進行比較的值 A
3.擬寫入的新值 B

更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B,否則不會執行任何操作。一般情況下是一個自旋操作,即不斷的重試。

 

自旋鎖

自旋鎖(spinlock):是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那么該線程將循環等待,然后不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環。

 

它是為實現保護共享資源而提出一種鎖機制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執行單元獲得鎖。但是兩者在調度機制上略有不同。對於互斥鎖,如果資源已經被占用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那里看是否該自旋鎖的保持者已經釋放了鎖,”自旋”一詞就是因此而得名。

 

Java實現自旋鎖

 1 package org.fool.hellojava.lock;  
 2   
 3 import java.util.concurrent.atomic.AtomicReference;  
 4   
 5 public class SpinLockTest {  
 6     private static SpinLock lock = new SpinLock();  
 7   
 8     public static void main(String[] args) {  
 9         new Thread(() -> {  
10             lock.lock();  
11   
12             test();  
13   
14             lock.unlock();  
15         }).start();  
16   
17         new Thread(() -> {  
18             lock.lock();  
19   
20             test();  
21   
22             lock.unlock();  
23         }).start();  
24   
25         new Thread(() -> {  
26             lock.lock();  
27   
28             test();  
29   
30             lock.unlock();  
31         }).start();  
32     }  
33   
34     public static void test() {  
35         System.out.println(Thread.currentThread().getName() + " invoked test...");  
36   
37         try {  
38             Thread.sleep(500);  
39         } catch (InterruptedException e) {  
40             e.printStackTrace();  
41         }  
42     }  
43   
44     private static class SpinLock {  
45         private AtomicReference<Thread> cas = new AtomicReference<>();  
46   
47         public void lock() {  
48             Thread currentThread = Thread.currentThread();  
49   
50             while (!cas.compareAndSet(null, currentThread)) {  
51                 System.out.println(Thread.currentThread().getName() + " waiting...");  
52             }  
53         }  
54   
55         public void unlock() {  
56             Thread currentThread = Thread.currentThread();  
57             cas.compareAndSet(currentThread, null);  
58         }  
59   
60     }  
61 }  

lock()方法利用的CAS,當第一個線程A獲取鎖的時候,能夠成功獲取到,不會進入while循環,如果此時線程A沒有釋放鎖,另一個線程B又來獲取鎖,此時由於不滿足CAS,所以就會進入while循環,不斷判斷是否滿足CAS,直到A線程調用unlock方法釋放了該鎖。

 

自旋鎖的缺點

1.如果某個線程持有鎖的時間過長,就會導致其它等待獲取鎖的線程進入循環等待,消耗CPU。使用不當會造成CPU使用率極高。
2.上面Java實現的自旋鎖不是公平的,即無法滿足等待時間最長的線程優先獲取鎖。不公平的鎖就會存在“線程飢餓”問題。

自旋鎖的優點

1.自旋鎖不會使線程狀態發生切換,一直處於用戶態,即線程一直都是active的;不會使線程進入阻塞狀態,減少了不必要的上下文切換,執行速度快
2.非自旋鎖在獲取不到鎖的時候會進入阻塞狀態,從而進入內核態,當獲取到鎖的時候需要從內核態恢復,需要線程上下文切換。 (線程被阻塞后便進入內核(Linux)調度狀態,這個會導致系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能)

Java實現可重入自旋鎖

為了實現可重入鎖,需要引入一個計數器,用來記錄獲取鎖的線程數。

 1 package org.fool.hellojava.lock;  
 2   
 3 import java.util.concurrent.atomic.AtomicReference;  
 4   
 5 public class ReentrantSpinLockTest {  
 6   
 7     private static ReentrantSpinLock lock = new ReentrantSpinLock();  
 8   
 9     public static void main(String[] args) {  
10         new Thread(() -> {  
11             lock.lock();  
12   
13             test();  
14   
15             lock.unlock();  
16         }).start();  
17     }  
18   
19     public static void test() {  
20         lock.lock();  
21         System.out.println(Thread.currentThread().getName() + " invoked test...");  
22   
23         try {  
24             Thread.sleep(5000);  
25         } catch (InterruptedException e) {  
26             e.printStackTrace();  
27         }  
28   
29         lock.unlock();  
30     }  
31   
32     private static class ReentrantSpinLock {  
33         private AtomicReference<Thread> cas = new AtomicReference<>();  
34         private int count;  
35   
36         public void lock() {  
37             Thread currentThread = Thread.currentThread();  
38   
39             if (currentThread == cas.get()) {  
40                 count++;  
41                 return;  
42             }  
43   
44             while (!cas.compareAndSet(null, currentThread)) {  
45                 // DO nothing  
46                 System.out.println(Thread.currentThread().getName() + " waiting...");  
47             }  
48         }  
49   
50         public void unlock() {  
51             Thread currentThread = Thread.currentThread();  
52             if (currentThread == cas.get()) {  
53                 if (count > 0) {  
54                     count--;  
55                 } else {  
56                     cas.compareAndSet(currentThread, null);  
57                 }  
58             }  
59         }  
60     }  
61 }  

 

自旋鎖與互斥鎖

1.自旋鎖與互斥鎖都是為了實現保護資源共享的機制。
2.無論是自旋鎖還是互斥鎖,在任意時刻,都最多只能有一個保持者。
3.獲取互斥鎖的線程,如果鎖已經被占用,則該線程將進入睡眠狀態;獲取自旋鎖的線程則不會睡眠,而是一直循環等待鎖釋放。

自旋鎖總結

1.自旋鎖:線程獲取鎖的時候,如果鎖被其他線程持有,則當前線程將循環等待,直到獲取到鎖。
2.自旋鎖等待期間,線程的狀態不會改變,線程一直是用戶態並且是活動的(active)。
3.自旋鎖如果持有鎖的時間太長,則會導致其它等待獲取鎖的線程耗盡CPU。
4.自旋鎖本身無法保證公平性,同時也無法保證可重入性。
5.基於自旋鎖,可以實現具備公平性和可重入性質的鎖。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM