一直以來不是怎麼清楚自旋鎖,最近有點時間,好好的學習了一下;
所謂的自旋鎖在我的理解就是多個線程在嘗試獲取鎖的時候,其中一個線程獲取鎖之後,其他的線程都處在一直嘗試獲取鎖的狀態,不會阻塞!!!那麼什麼叫做一直嘗試獲取鎖呢?就是一個循環,比較經典的是AtomicInteger中的一個updateAndGet方法,下圖所示(當然也可以直接看unsafe類中的getAndAddInt等類似方法);
我們可以看出在while循環中使用CAS去嘗試更新一個變量,如果更新失敗,就會一直在這個循環中一直在嘗試;成功的話,就可以到最後的return語句;
由此我們可以大概知道如果自旋的線程過多,那麼CPU的資源就會被大量消耗!!!
順便提一個東西叫做原子引用,官方提供了AtomicInteger,AtomicBoolean等原子類,那麼如果我們自己定義的類也需要有原子性怎麼辦呢?所以官方提供了一個AtomicReference類,可以將我們自己定義的類封裝一下,就成了我們自己的原子類,例如AtomicReference<Student> atomicReference = new AtomicReference<>();,然後我們對Student的實例進行CAS各種CAS操作;
栗子:
package TestMain; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @Slf4j public class TestMain80 { //一個Thread類的原子引用 AtomicReference<Thread> atomicReference = new AtomicReference<>(); //加鎖的方法 public void myLock() { Thread currentThread = Thread.currentThread(); log.info("myLock--Thread:{}", currentThread.getName()); //這個就是自旋鎖的核心,利用CAS比較當前原子引用中是否爲null,如果是null,就把當前線程A放到裏面去, // 此時線程B再到這裏,那麼就會CAS失敗,一直在while循環中 while (!atomicReference.compareAndSet(null, currentThread)) { } } //解鎖的方法 public void myUnlock() { Thread currentThread = Thread.currentThread(); //CAS比較原子引用中是不是線程A,是的話就更新爲null,此時在上面while中一直在自旋的線程B就可以跳出來了 atomicReference.compareAndSet(currentThread, null); log.info("myUnlock--Thread:{}", currentThread.getName()); } public static void main(String[] args) { TestMain80 testMain80 = new TestMain80(); //線程A,首先加鎖,然後等3秒中,然後釋放鎖 new Thread(() -> { testMain80.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } testMain80.myUnlock(); }, "A").start(); //主線程等1秒,保證A線程先執行 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //線程B,加鎖再釋放鎖 new Thread(() -> { testMain80.myLock(); testMain80.myUnlock(); }, "B").start(); } }
上面的就是一個自旋鎖的栗子,執行結果中首先是執行A線程的myLock方法,獲取鎖成功,之後的B線程雖然也會執行mylock方法,但是會在while循環中一直阻塞,直到線程A調用了myUnlock方法釋放鎖,最後兩行纔會打印出來;