悲觀 / 樂觀鎖原理與運用

樂觀鎖:

不會加鎖,只是更新共享資源時,判斷是否允許更新。

一. CAS(Compare and Swap)思想:

需要讀寫的內存值V,進行比較的預期值A,要寫入的新值B。

當且僅當預期值A和內存值V相同時,才允許將內存值V修改爲B;否則該線程將自己的A更新爲新的V值,繼續自旋等待,直至檢測到A值與V值相等時,才更新爲B。

使用 Unsafe.compareAndSwapObject() 保證線程安全用例

如下的用例中 UNSAFE.compareAndSwapObject(unsafeCASTest, I_OFFSET, unsafeCASTest.i, unsafeCASTest.i + 1); 完成了多線程安全的 i+1 操作。

/**
 * @Author Snail
 * @Describe 通過Unsafe對多個線程操作i++實現線程安全問題
 * @CreateTime 2020/3/11
 */
public class UnsafeCASTest {
    private int i = 0;
    private static Unsafe UNSAFE;
    static long I_OFFSET;//i的偏移量

    static {
        try {
            //該方法無法獲取到UNSAFE類,需要通過下面的反射去獲取
//            UNSAFE = Unsafe.getUnsafe();
            // 獲取 Unsafe 內部的私有的實例化單例對象
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            // 無視權限
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);

            I_OFFSET = UNSAFE.objectFieldOffset(UnsafeCASTest.class.getDeclaredField("i"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void main() {
        UnsafeCASTest unsafeCASTest = new UnsafeCASTest();
        new Thread(new ThreadAdd(unsafeCASTest)).start();
        new Thread(new ThreadAdd(unsafeCASTest)).start();

        new Scanner(System.in).nextLine();//bio阻塞當前執行線程
    }

    class ThreadAdd implements Runnable {

        private final UnsafeCASTest unsafeCASTest;

        public ThreadAdd(UnsafeCASTest unsafeCASTest) {
            this.unsafeCASTest=unsafeCASTest;
        }

        @Override
        public void run() {
            while (true) {
//                i++;
                boolean b = UNSAFE.compareAndSwapObject(unsafeCASTest, I_OFFSET, unsafeCASTest.i, unsafeCASTest.i + 1); 
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "::" + i);
            }

        }
    }
}


Java基於 CAS 提供的操作類

java.util.concurrent.atomic包下有AtomicInteger、AtomicBoolean、AtomicLong等,都使用了CAS的機制,使用CPU的執行來保證原子性

//關於AtomicInteger的一個簡單用例
        AtomicInteger atomicInteger=new AtomicInteger(0);
        int i = atomicInteger.addAndGet(2);
        System.out.println(i);//2
        System.out.println(atomicInteger);//2
        int andAdd = atomicInteger.getAndAdd(3);
        System.out.println(andAdd);//2
        System.out.println(atomicInteger);//5

Q:CAS包含了Compare和Swap兩個操作,它又如何保證原子性呢?

A:CAS是由CPU支持的原子操作,其原子性是在硬件層面進行保證的。

Q:CAS 有那些缺點?
A:

1. ABA問題

2. 高競爭環境下,自旋對CPU資源的消耗

3. 不夠靈活,只能保證一個共享變量的原子操作,涉及到多個變量的同步時,CAS無法保證安全

二. 版本號控制:

當要提交一個更新的時候,比較讀取時候的版本號,如果版本號一致,允許提交更新,否則返回失敗

悲觀鎖:

開始操作時,就加鎖,操作完成後再釋放鎖。

Lock(ReentrantLock)與Synchronized的對比:

類別 synchronized Lock
存在層次 Java的關鍵字,在jvm層面上 是一個類
鎖的釋放 1、以獲取鎖的線程執行完同步代碼,釋放鎖 2、線程執行發生異常,jvm會讓線程釋放鎖 (在其底層原理可以看到) 必須在finally中釋放鎖,不然容易造成線程死鎖
鎖的獲取 假設A線程獲得鎖,B線程等待。如果A線程阻塞,B線程會一直等待 Lock有多個鎖獲取的方式,如lock.lock(),lock.tryLock(),
鎖當前狀態 無法判斷 可以判斷(trylock)
鎖類型 可重入、不可中斷、非公平 可重入、可中斷、可公平(初始化時傳入布爾值)
性能 少量同步 大量同步

鎖類型的解釋:

  1. 可重入鎖:在執行一個對象中所有同步方法時,不用再次去獲取鎖

  2. 公平鎖:按線程等待時間長短來獲取鎖,等待時間長的具有優先獲取鎖權利。

ReentrantLock默認非公平鎖,實例化時傳入boolean值決定是否使用公平鎖

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
  1. 可中斷鎖:如果線程A使用的是可中斷方式(lock.lockInterruptibly();),線程A在嘗試獲取鎖的過程中,如果在獲取不到鎖的過程中,被中斷,那麼線程A將能夠感知到這個中斷,而不是一直阻塞下去(如果是不可中斷的鎖,那麼線程A將一直阻塞)。

關於Lock和synchronized的比較可參考

關於使用lock中的方法用例

兩種鎖底層的實現:

synchronized:對應兩條指令 monitorenter(獲取鎖monitorexit(釋放鎖) ,每一個 monitorenter 都對應兩個monitorexit,一個用於正常執行完成釋放鎖,一個用於執行異常時釋放鎖

ReentrantLock:主要使用 AbstractQueuedSynchronizer(AQS)中的 volatile 修飾的同步狀態位state、CAS 修改 state 和 CLH 的線程隊列實現的一種獨佔鎖。

AQS的更多分析與介紹

在 JDK 6 後對鎖進行了優化,Synchronized 效率上升到與 Lock 持平。那你談談 JVM對鎖的優化:

使用如下的一些規則來優化:

自適應自旋鎖:爲避免線程間的頻繁切換,JVM按情況給出適合的自旋時間

鎖消除:對檢測到不可能存在共享數據競爭的鎖進行消除

鎖粗化:當一連串的操作都在對同一個對象加鎖時,JVM會擴大鎖範圍

偏向鎖:爲減少同一線程多次獲取同一個鎖的性能消耗

輕量級鎖:是在線程已有偏向鎖的狀態時,更新到輕量級鎖

更多鎖優化信息的詳細介紹

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