天天看點

Java鎖-自旋鎖

1、什麼是自旋鎖

  自旋鎖是為實作保護共享資源而提出一種鎖機制,用于多線程同步的一種鎖,線程反複檢查鎖變量是否可用。由于線程在這一過程中保持執行,是以是一種忙等待。它是一種非阻塞鎖,也就是說,如果某線程需要擷取鎖,但該鎖已經被其他線程占用時,該線程不會被挂起,而是在不斷的消耗CPU的時間,不停的試圖擷取鎖。

  自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個執行單元獲得鎖。但是兩者在排程機制上略有不同。對于互斥鎖,如果資源已經被占用,資源申請者隻能進入睡眠狀态。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被别的執行單元保持,調用者就一直循環在那裡看是否該自旋鎖的保持者已經釋放了鎖,這就是"自旋"。

2、Java是如何實作自旋鎖?

手寫demo實作自旋鎖,

import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
    private AtomicReference<Thread> cas = new AtomicReference<Thread>();

    public void lock() {
        Thread current = Thread.currentThread();
        while (!cas.compareAndSet(null, current)) {  // 利用CAS
            // 什麼都不做,自旋
        }
        System.out.println(Thread.currentThread().getName()+"\t get lock");
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        cas.compareAndSet(current, null);
        System.out.println(Thread.currentThread().getName()+"\t release lock");
    }
}           

并測試代碼,線程使用自旋方式擷取鎖,然後模拟2s的邏輯處理任務,

ExecutorService executorService = Executors.newFixedThreadPool(2);
        final CountDownLatch countDownLatch = new CountDownLatch(2);
        final SpinLock spinLock = new SpinLock();
        for (int i = 0 ; i < 2 ; i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    spinLock.lock();
                    System.out.println(Thread.currentThread().getName()+"\t開始運作");
                    ++count;
                    SystemClock.sleep(2000);
                    System.out.println(Thread.currentThread().getName()+"\t結束運作");
                    spinLock.unlock();
                    countDownLatch.countDown();
                }
            });

        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(count);
}           

運作結果:

  通過代碼可以看出,自旋就是在循環判斷條件是否滿足,如果鎖被占用很長時間的話,自旋的線程等待的時間也會變長,白白浪費掉處理器資源。是以在JDK中,自旋操作預設10次,我們可以通過參數“-XX:PreBlockSpin”來設定,當超過來此參數的值,則會使用傳統的線程挂起方式來等待鎖釋放。

  但是自旋鎖也是有優點的,自旋鎖不會使線程狀态發生切換,一直處于使用者态,即線程一直都是active的;不會使線程進入阻塞狀态,減少了不必要的上下文切換,執行速度快。非自旋鎖在擷取不到鎖的時候會進入阻塞狀态,進而進入核心态,當擷取到鎖的時候需要從核心态恢複,需要線程上下文切換,嚴重影響鎖的性能。

3、自适應自旋鎖

  随着JDK的更新,在1.6的時候,出現了“自适應自旋鎖”。所謂的“自适應”意味着對于同一個鎖對象,線程的自旋時間是根據上一個持有該鎖的線程的自旋時間以及狀态來确定的。例如對于A鎖對象來說,如果一個線程剛剛通過自旋獲得到了鎖,并且該線程也在運作中,那麼JVM會認為此次自旋操作也是有很大的機會可以拿到鎖,是以它會讓自旋的時間相對延長。但是如果對于B鎖對象自旋操作很少成功的話,JVM甚至可能直接忽略自旋操作。

   是以,自适應自旋鎖是一個更加智能,對我們的業務性能更加友好的一個鎖。

4、JAVA自旋鎖應用

  Jdk 提供的java.util.concurrent.atomic包裡面提供了一組原子類。 基本上就是目前擷取鎖的線程,執行更新的方法,其他線程自旋等待,比如atomicInteger類中的getAndAdd方法内部實際上使用的就是Unsafe的方法。