原創轉載請注明出處: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.基於自旋鎖,可以實現具備公平性和可重入性質的鎖。