多線程6 - CAS和AQS的應用和原理

java 裏有兩種鎖

  • synchronized (jvm內部實現)
  • lock ( jdk源碼實現)
synchronized 在jdk6之前加鎖方式是重量級鎖,之後因爲Lock鎖的出現,synchronized進行了優化,纔有的偏向鎖/輕量級鎖,兩者性能差不多,但lock提供了一些比synchronized更高級的功能。
 

那麼Doug Lea是怎樣寫的同步鎖呢?

 

我們先了解一些要用到的知識:

CAS:全稱compare and swap(比較交換),比較三個值,內存裏的值(O)、預期值(E)、新值(N),在進行CAS操作時,會比較O 和 E 是否相等,相等就把N的值賦值給O,可以理解爲修改密碼,數據庫值(O),驗證舊密碼(E),新密碼(N)驗證通過,修改成功。

UNSAFE:給jdk提供最底層的方法,包括volatile、線程調度、CAS相關、內存相關等功能。

嘗試寫一個同步鎖,可能的方式如下:

1.使用自旋

2.sleep+自旋

3.yield+自旋

4.park+自旋

下面使用了AtomicInteger類,他也是Doug lea寫的,底層調用unsafe類實現,即借用它來實現。

第一種方案是自旋

讓線程去加鎖,加鎖失敗的就一直空轉,它會一直消耗CPU資源
 
public class SlipLock {
    // 0:無鎖
    private volatile AtomicInteger status = new AtomicInteger(0);

    public void lock(){
        while (!status.compareAndSet(0, 1)) {
        }
    }

    public  void unlock(){
        status.set(0);
    }
}
public static void spinLockTest(){
    List<Thread> list = new ArrayList<>();
    SlipLock lock = new SlipLock();
    for (int i = 0; i < 10; i++) {
        Thread t1 = new Thread(()->{
            lock.lock();
            for (int j = 0; j < 10; j++) {
                System.out.print(Thread.currentThread().getName()+" "+j);
            }
            System.out.println();
            lock.unlock();
        },"線程-"+i);
        list.add(t1);
    }
    list.forEach(t->t.start());
}

第二種方案:yield+自旋

自旋鎖讓線程一直處於空轉狀態,並沒有讓出CPU,使用yield可以讓出CPU,但是並不能保證下一次不是該線程。
而且yield方法只會給相同優先級或更高優先級的線程機會。
 
public class YieldLock {
    // 0:無鎖
    private AtomicInteger status = new AtomicInteger(0);

    public void lock(){
        while (!status.compareAndSet(0, 1)) {
            Thread.yield();
        }
    }

    public void unlock(){
        status.set(0);
    }
}
public static void yieldLockTest() {
    List<Thread> list = new ArrayList<>();
    YieldLock lock = new YieldLock();
    for (int i = 0; i < 10; i++) {
        Thread t1 = new Thread(()->{
            lock.lock();
            for (int j = 0; j < 10; j++) {
                System.out.print(Thread.currentThread().getName()+" "+j);
            }
            System.out.println();
            lock.unlock();
        },"線程-"+i);
        list.add(t1);
    }
    list.forEach(t->t.start());
}

第三種方案:sleep+自旋

和yield不一樣,他不考慮優先級問題
public class SleepLock {
    // 0:無鎖
    private volatile AtomicInteger status = new AtomicInteger(0);

    public void lock(){
        while (!status.compareAndSet(0, 1)) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public  void unlock(){
        status.set(0);
    }
}
public static void sleepLockTest() {
    List<Thread> list = new ArrayList<>();
    SleepLock lock = new SleepLock();
    for (int i = 0; i < 10; i++) {
        Thread t1 = new Thread(()->{
            lock.lock();
            for (int j = 0; j < 10; j++) {
                System.out.print(Thread.currentThread().getName()+" "+j);
            }
            System.out.println();
            lock.unlock();
        },"線程-"+i);
        list.add(t1);
    }
    list.forEach(t->t.start());
}

第四種方案:park

unsafe提供給jdk底層使用,當我們使用時會堅持是否時“受信任”的類,jdk也提供了第三方工具類LockSupport;

這裏park會阻塞線程,然後再調用unpark的時候,從park方法出繼續執行;這樣的方式使得線程按排隊時間長短獲取鎖,比上面的方法更爲公平。

public class ParkLook {
    // 0:無鎖
    private AtomicInteger status = new AtomicInteger(0);

    // 裝載等待的線程
    private List<Thread> list = new ArrayList<>();

    public void lock(){
        while (!status.compareAndSet(0, 1)) {
            list.add(Thread.currentThread());
            LockSupport.park();
        }
    }

    public void unlock(){
        status.compareAndSet(1,0);
        if(list.size() > 0) {
            Thread thread = list.get(0);
            list.remove(0);
            LockSupport.unpark(thread);
        }
    }
}
public static void parkLockTest(){
    List<Thread> list = new ArrayList<>();
    ParkLook lock = new ParkLook();
    for (int i = 0; i < 10; i++) {
        Thread t1 = new Thread(()->{
            lock.lock();
            for (int j = 0; j < 10; j++) {
                System.out.print(Thread.currentThread().getName()+" "+j);
            }
            System.out.println();
            lock.unlock();
        },"線程-"+i);
        list.add(t1);
    }
    list.forEach(t->t.start());
}

在最後一個方法中就已經能夠看出AQS的一個樣貌了,那麼什麼是AQS呢?

AbstractQueuedSynchronizer簡稱AQS,Doug lea設計的提供實現阻塞鎖和一些列依賴FIFO等待隊列的同步器框架。在synchronized優化之前Doug lea寫的這個併發框架性能是很高的,小米首席架構師崔寶秋說過:要多看優秀的代碼,這樣才能寫出優秀的代碼。下一篇我們就慢慢看源碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章