Java基礎-Java中常用的鎖機制與使用

Java基礎-Java中常用的鎖機制與使用

lock或互斥mutex是一種同步機制,主要用於在存在多線程的環境中強制對資源進行訪問限制。鎖的主要作用爲強制實施互斥排他以及併發控制策略。鎖一般需要硬件支持纔可以有效的實施。這種支持通常採取一個或多個原子指令的形式,如test-and-setfetch-and-add或者compare-and-swap。這些指令允許單個進程測試鎖是否空閒,如果空閒,則通過單個原子操作獲取鎖。

使用鎖可以保證多線程的環境下同步執行,可以解決可見性/有序性/原子性問題。

一、鎖的類型

Java主流鎖
線程是否需要鎖住同步資源?
鎖住
悲觀鎖
不鎖住
樂觀鎖
鎖住同步資源失敗,線程是否需要阻塞?
阻塞
不阻塞
自旋鎖
適應性自旋鎖
多個線程競爭同步資源的流程細節有沒有區別?
不鎖住資源,多個線程中只有一個能修改資源成功,其他線程就重試
無鎖
同一個線程執行同步資源時自動獲取資源
偏向鎖
多個線程競爭同步資源時,沒有獲取資源的線程自旋等待鎖釋放
輕量級鎖
多個線程競爭同步資源時,沒有獲取資源的線程阻塞等待喚醒
重量級鎖
多個線程競爭鎖時是否需要排隊?
排隊
公平鎖
先嚐試插隊,插隊失敗再排隊
非公平鎖
一個線程的多個線程能不能獲取同一把鎖?
可重入鎖
不能
非可重入鎖
多個線程能不能共享一把鎖?
共享鎖
不能
排他鎖

二、CAS

CAS(Compare And Swap),比較並交換,爲樂觀鎖

適用場景分析

CAS只能針對單個變量進行原子操作;不能操作代碼塊。(AtomicReference原子引用類可解決多屬性變量的問題),適用於資源競爭較少(線程衝突較輕)的情況,自旋(循環)時間不會很長,不會浪費cpu太多資源。

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicityDemo {

    private static final AtomicInteger number = new AtomicInteger(1);

    public static void main(String[] args) throws InterruptedException {
        ArrayList<Thread> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    number.getAndIncrement();
                }
            });
            thread.start();
            list.add(thread);
        }
        for (Thread thread : list) {
            thread.join();
        }
        number.decrementAndGet();
        System.out.println("number:" + number);
    }
}

悲觀鎖與樂觀鎖

悲觀鎖

線程A嘗試獲取鎖
線程B嘗試獲取鎖
線程A執行中
等待
線程A執行完畢,釋放鎖
線程B嘗試獲取鎖
線程B獲取鎖成功,並開始操作同步資源
線程A
同步資源
線程B
線程A
線程B
同步資源
線程A
線程B
同步資源

樂觀鎖

獲取數據後直接操作
準備更新同步資源
先判斷內存中同步資源是否被更新
同步資源沒有被更新
同步資源被其他線程更新,更新或嘗試
更新內存中同步資源的值
線程A
同步資源
線程A
同步資源
線程A
線程A
同步資源
  • 悲觀鎖適合寫多讀少的場景,因爲先加鎖能保證寫操作的正確性。
  • 樂觀鎖適合讀多寫少的場景,因爲讀操作一般並不需要加鎖(沒有進行數據修改操作),因此樂觀鎖的無鎖特性能使讀性能有很大的提升(減少了加鎖等待的時間)。

CAS原理

Y
N
開始
獲取內存中的值A
此時內存中的值V==預期值A?
更新內存中的值B
結束
  • CAS有三個操作數,即內存值 v,舊操作數a,新操作數b。當需要更新vb時,需要先判斷v值是否與之前的所見值a相同,若相同則將v賦值爲b,若不同,則什麼都不做。
  • CAS是一種非阻塞算法。利用JNI機制和CPUlock cmpxchg...指令來完成。

JNI機制:https://www.cnblogs.com/kexinxin/p/11689641.html

自旋鎖/自適應自旋鎖

自旋鎖

當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷 鎖是否能夠被成功獲取,直到獲取到鎖纔會退出循環。

優點

阻塞或喚醒一個Java線程需要操作系統切換 CPU 狀態來完成,這種狀態轉換需要耗費處理器時間。如果同步代碼塊中的內容過於簡單,狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長。所以自旋鎖就可以避免 CPU切換帶來的性能、時間等影響。

缺點

自旋等待雖然避免了線程切換的開銷,但它要佔用處理器時間。如果鎖被佔用的時間很長,那麼自旋的線程只會白浪費處理器資源。

使用方式

一個基於CAS實現的自旋鎖:AtomicInteger

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4
    return var5;
}

do while循環操作中的判斷條件compareAndSwapInt方法。整個“比較+更新”操作封裝在compareAndSwapInt()中,整個過程就是自旋的去重試這個操作。

自定義自旋鎖

定義鎖

import java.util.concurrent.atomic.AtomicReference;

/**
 * @program: Demo
 * @description: 自定義自旋鎖
 * @author: 嵐櫻時
 * @date: 2020-05-26 09:43
 */
public class CustomSpinLock {

    /**
     * 存放當前持有鎖的線程
     * 當reference爲null,鎖沒有被線程獲取
     */
    private AtomicReference<Thread> reference = new AtomicReference<>();

    /**
     * 獲取鎖
     */
    public void lock(){
        Thread currentThread = Thread.currentThread();
        while (!reference.compareAndSet(null, currentThread)) {
            // 自旋判斷鎖是否被其他線程持有
            // reference爲null時,當前鎖沒有被線程獲取,compareAndSet返回true
            // TODO
        }
    }

    /**
     * 釋放鎖
     */
    public void unLock(){
        Thread currentThread = Thread.currentThread();
        if (reference.get() != currentThread) {
            // 鎖被其他線程佔用,當前線程無法釋放鎖
            return;
        }
        // 釋放鎖
        reference.set(null);
    }

}

測試自定義鎖

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @program: Demo
 * @description: 自定義自旋鎖測試類
 * @author: 嵐櫻時
 * @date: 2020-05-26 10:15
 */
public class TestCustomSpinLock {

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        // 創建線程池
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        // 創建計數器
        // 計數器的初始值是線程的數量,每當一個線程執行完成之後,計數器的值-1
        // 當計數器的值爲0,表示所有線程都執行完畢,然後在閉鎖上等待的線程就可以恢復工作
        CountDownLatch countDownLatch = new CountDownLatch(100);
        // 創建自定義自旋鎖
        CustomSpinLock customSpinLock = new CustomSpinLock();
        for (int i = 0; i < 100; i++) {
            executorService.execute(() -> {
                customSpinLock.lock();
                ++count;
                customSpinLock.unLock();
                // 釋放鎖後,計數器值-1
                countDownLatch.countDown();
            });
        }
        // 調用await()方法,線程被掛起,等待計數器爲0後繼續執行
        countDownLatch.await();
        System.out.println(count);
        executorService.shutdown();
    }
}
  • lock()方法利用的CAS,當第一個線程A獲取鎖的時候,成功獲取到鎖,不會進入while循環。
  • 如果此時線程A沒有釋放鎖,另一個線程B想獲取鎖,此時由於不滿足CAS,所以就會進入while循環,不斷判斷是否滿足CAS,直到線程A調用unlock方法釋放鎖,線程B纔會獲取鎖。
啓用自旋鎖
參數 默認值或限制 說明
-XX:-UseSpinning java1.4.2和1.5需要手動啓用,java6默認已啓用 啓用多線程自旋鎖優化
-XX:PreBlockSpin=10 -XX:+UseSpinning必須先啓用。對於jdk6來說已經默認啓用了,默認自旋10次。 控制多線程自旋鎖優化的自旋次數

自適應自旋鎖

自適應意味着對於一個鎖對象,線程的自旋時間是根據上一個持有該鎖的線程的自旋時間以及狀態來確定的。

例如:對於鎖A對象來說,如果一個線程剛剛通過自旋獲得了鎖,並且該線程也在運行中,那麼JVM會認爲此次自旋操作也是有很大的機會可以拿到鎖,因此它會讓自旋的時間相對延長。但是如果對於鎖B對象自旋操作很少成功的話,JVM甚至可能直接忽略自旋操作,將線程掛起或堵塞。

ABA問題

線程1準備用CAS修改變量A,在此之前,其他線程將變量的值由A替換爲B,又由B替換爲A,然後線程1執行CAS時發現變量的值仍然爲A,所以CAS成功。但是實際上這時的現場已經和最初不同。

A
B
A

代碼示例

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @program: Demo
 * @description: 測試ABA問題案例
 * @author: 嵐櫻時
 * @date: 2020-05-26 11:00
 */
public class AtomicDemo {

    public static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("當前線程:" + Thread.currentThread() + "\n初始值:" + atomicInteger);
            try {
                // 線程1等待,線程2對數據進行修改
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // CAS 期望1->2
            boolean CASStatus = atomicInteger.compareAndSet(1, 2);
            System.out.println("當前線程:" + Thread.currentThread() + "\nCAS操作結果:" + CASStatus);
        }, "線程1");

        Thread thread2 = new Thread(() -> {
            try {
                // 線程等待,確保線程1先執行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // atomicInteger+1=>2
            atomicInteger.incrementAndGet();
            System.out.println("當前線程:" + Thread.currentThread() + "\nincrementAndGet結果:" + atomicInteger);
            // atomicInteger-1=>1
            atomicInteger.decrementAndGet();
            System.out.println("當前線程:" + Thread.currentThread() + "\ndecrementAndGet結果:" + atomicInteger);
        }, "線程2");

        thread1.start();
        thread2.start();
    }
}
解決方案

AtomicStampedReference主要維護一個對象引用以及一個可以自動更新的整數stamppair對象來解決ABA問題。

A:stamp1
B:stamp2
A:stamp3

AtomicStampedReference源碼

package java.util.concurrent.atomic;

/**
 * An {@code AtomicStampedReference} maintains an object reference
 * along with an integer "stamp", that can be updated atomically.
 *
 * <p>Implementation note: This implementation maintains stamped
 * references by creating internal objects representing "boxed"
 * [reference, integer] pairs.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <V> The type of object referred to by this reference
 */
public class AtomicStampedReference<V> {

    private static class Pair<T> {
      	// 維護對象引用
        final T reference;
      	// 用於標誌版本
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair;
  
  	/****/
 
  	/**
  	 	* expectedReference	更新之前的原始值
  		* newReference			將要更新的新值
  		* expectedStamp			期待更新的標誌版本
  		* newStamp					將要更新的標誌版本
  		*/
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
  	
  	/****/
}

測試代碼

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @program: Demo
 * @description: 解決ABA案例
 * @author: 嵐櫻時
 * @date: 2020-05-26 12:50
 */
public class AtomicDemo1 {

    private static final AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 0);

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("當前線程:" + Thread.currentThread() + "\n初始值a:" + atomicStampedReference.getReference() + "\n");
            // 獲取當前標識版本
            int stamp = atomicStampedReference.getStamp();
            try {
                // 線程等待3s,保證干擾線程執行
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 判斷reference&&stamp
            boolean CASStatus = atomicStampedReference.compareAndSet(1, 2, stamp, stamp + 1);
            System.out.println("當前線程:" + Thread.currentThread() + "\nCAS判斷結果:" + CASStatus + "\n");
        }, "線程1");

        Thread thread2 = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("當前線程:" + Thread.currentThread() + "\nincrement值:" + atomicStampedReference.getReference()+"\n");
            atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("當前線程:" + Thread.currentThread() + "\ndecrement值:" + atomicStampedReference.getReference() + "\n");
        },"線程2");

        thread1.start();
        thread2.start();
    }
}

三、synchronized

使用synchronized關鍵字,可以解決可見性/有序性/原子性問題。

使用方式

  • 同步實例方法,鎖是當前實例對象
  • 同步類方法,鎖是當前類對象
  • 同步代碼塊,鎖是括號裏面的對象
/**
 * @program: Demo
 * @description: Synchronized關鍵字使用方式案例
 * @author: 嵐櫻時
 * @date: 2020-05-26 13:38
 */
public class SynchronizedDemo {

    static int counter = 0;

    static final Object lock = new Object();

    public static void main(String[] args) {
        // 鎖是括號裏面的對象
        synchronized (lock) {
            counter++;
        }
    }

    /**
     * 鎖是當前類對象
     */
    public synchronized static void m1(){
        counter++;
    }
}

特性

原子性

原子性就是指一個操作或者多個操作,要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

可見性

synchronized對一個類或對象加鎖時,一個線程如果要訪問該類或對象必須先獲得它的鎖,而這個鎖的狀態對於其他任何線程都是可見的,並且在釋放鎖之前會將對變量的修改刷新都主存當中,保證資源變量的可見性,如果某個線程佔用了該鎖,其他線程就必須在鎖池中等待鎖的釋放。

有序性

synchronized保證了每個時刻都只有一個線程訪問同步代碼塊,也就確定了線程執行同步代碼塊是分先後順序的,保證了有序性。

可重入性

當一個線程再次請求自己持有對象鎖的臨界資源時,這種情況屬於重入鎖。就是一個線程擁有了鎖仍然還可以重複申請該鎖。

偏向鎖

原理

在沒有多線程競爭的情況下,輕量級鎖的獲取以及釋放多次CAS原子指令,而偏向鎖只依賴一次CAS原子指令置換ThreadID,不過一旦出現多個線程競爭時必須撤銷偏向鎖,所以撤銷偏向鎖消耗的性能必須小於之前節省下來的CAS原子操作的性能消耗。

  • JDK1.6之後默認開啓偏向鎖
  • 偏向鎖延遲生效,在應用程序啓動幾秒鐘之後才激活,可以使用
  • XX:BiasedLockingStartupDelay=0 參數關閉延遲
  • 如果確定應用程序中所有鎖通常情況下處於競爭狀態,可以通過-XX:-UseBiasedLocking參數關閉偏向鎖。

偏向鎖獲取

  • 通過markup mark = obj->mark()獲取對象的markOop數據mark,即對象頭的Mark Word

  • 判斷mark是否爲可偏向狀態,即mark的偏向鎖標誌位爲1,鎖標誌位爲01

  • 判斷markJavaThread的嗎狀態

    • 如果爲空,則進入步驟4
    • 如果指向當前線程,則執行同步代碼塊
    • 如果指向其他線程,進入步驟5
  • 通過CAS原子指令設置mark中的JavaThread爲當前線程ID,執行CAS成功,執行同步代碼塊

  • 如果執行CAS失敗,表示當前存在多個線程競爭鎖,火的偏向鎖的線程達到全局安全點(safepoint)時被掛起,撤銷偏向鎖,並升級爲輕量級。升級完成後被阻塞在安全點的線程繼續執行同步代碼塊

偏向鎖撤銷

只有當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖,偏向鎖的撤銷由BiasedLocking::revoke_at_safepoint方法實現。

  • 偏向鎖的撤銷動作必須等待全局安全點
  • 暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定的狀態
  • 撤銷偏向鎖,恢復到無鎖(標誌位爲01)(直接調用對象的hashcode方法)或輕量級鎖(標誌位爲00)(多線程交替獲取對象鎖)的狀態

實踐

偏向鎖的開啓關閉

依賴包

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

代碼案例實現

import org.openjdk.jol.info.ClassLayout;

/**
 * @program: Demo
 * @description: 偏向鎖Demo
 * @author: 嵐櫻時
 * @date: 2020-05-26 14:35
 */
public class BiasedLockDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("開啓偏向鎖");
        biasedLockStart();
        System.out.println("關閉偏向鎖");
        biasedLockClose();
    }

    /**
     * 開啓偏向鎖
     * @throws InterruptedException
     */
    public static void biasedLockStart() throws InterruptedException{
        // 加鎖前,偏向鎖生效有延遲,不會立即生效,輸出001
        String result = ClassLayout.parseInstance(new BiasedLockDemo.Model()).toPrintable();
        System.out.println("result:" + result);
        Thread.sleep(5000);

        // 加鎖前,延遲一定時間後,偏向鎖生效,輸出101
        Model model = new BiasedLockDemo.Model();
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println("result:" + result);

        synchronized (model) {
            result = ClassLayout.parseInstance(model).toPrintable();
            System.out.println("result:" + result);
        }

        // 解鎖後,對象頭不變
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println("result:" + result);
    }

    /**
     * 關閉偏向鎖
     * @throws InterruptedException
     */
    public static void biasedLockClose() throws InterruptedException {
        // 加鎖前,輸出001
        String result = ClassLayout.parseInstance(new Model()).toPrintable();
        System.out.println(result);
        Thread.sleep(5000);
        System.out.println("================================================");

        // 加鎖前,延遲i一定時間後,仍輸出001
        Model model = new Model();
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println(result);
        System.out.println("================================================");

        // 加鎖時,輸出輕量級鎖信息 線程棧中的lock record鎖記錄指針和00狀態位
        synchronized (model) {
            result = ClassLayout.parseInstance(model).toPrintable();
            System.out.println(result);
            System.out.println("================================================");
        }

        // 解鎖後,輸出001,無鎖狀態
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println(result);
    }

    public static class Model{

    }
}
開啓偏向鎖
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

關閉偏向鎖
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================================================
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================================================
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================================================
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

輕量級鎖

在多線程交替執行同步塊的情況下,儘量避免重量級鎖引起的性能消耗,直接使用輕量級鎖。但是如果多個線程在同一時刻進入臨界區,會導致輕量級鎖膨脹升級重量級鎖,所以輕量級鎖的出現並非是要替代重量級鎖。

輕量級鎖獲取

當關閉偏向鎖功能,或多個線程競爭偏向鎖導致鎖升級爲輕量級鎖,會嘗試獲取輕量級鎖,其入口位於ObjectSynchronizer::slow_enter

輕量級鎖釋放

輕量級鎖的釋放通過ObjectSynchronizer::fast_exit完成

  • 取出在獲取輕量級鎖時保存在BasicLock對象的mark數據dhw
  • 通過CAS嘗試把dhw替換到當前的Mark Word,如果CAS成功,說明成功的釋放了鎖,否則執行步驟3
  • 如果CAS失敗,說明有其他線程在嘗試獲取鎖,這是該鎖已經升級爲重量級鎖。執行重量級鎖釋放流程。

重量級鎖

重量級鎖通過對象內部的監視器monitor實現,其中monitor的本質是依賴於底層操作系統的MutexLock實現,操作系統實現線程之間的切換需要從用戶態到內核態的切換,切換成本非常高。

鎖膨脹過程通過ObhectSynchronizer::inflate函數實現。

monitor對象

  • 當一個線程需要獲取Object的鎖時,會被放入EntrySet中進行等待。
  • 如果該線程獲取到了鎖,成爲當前的owner。
  • 如果根據程序邏輯,一個獲得了鎖的線程缺少某些外部條件,而無法繼續進行下去(例如生產者發現隊列已滿或者消費者發現隊列爲空),那麼該線程可以通過調用wait方法將鎖釋放,進入wait set中阻塞進行等待。
  • 其他線程在這個時候有機會獲得鎖,去進行其他的任務,從而使之前不成立的外部條件成立,這樣先前被阻塞的線程就可以重新進入EntrySet去競爭鎖。

鎖膨脹

各種鎖比較

優點 缺點 使用場景
偏向鎖 加鎖不需要額外消耗,執行非同步方法僅有納秒差距。 如果線程見存在競爭,會帶來額外的撤銷鎖的消耗。 只有一個線程訪問同步塊場景。
輕量級鎖 競爭的線程不會堵塞,提高了程序的響應速度。 如果始終得不到鎖,競爭的線程使用自旋,會消耗CPU 追求響應時間,同步塊指向性速度非常快。
重量級鎖 線程競爭不會自旋,不消耗CPU。 線程堵塞,響應時間慢。 追求吞吐量,同步塊執行時間較長。

鎖消除

鎖消除是虛擬機對鎖的優化處理,JIT即使編譯器(把熱點代碼編譯成與本地平臺相關的機器碼,並且進行各種層次的優化)對運行上下文進行掃描,去除不可能存在競爭的鎖,比如下面兩個方法的執行效率是一樣的,由於object鎖是私有變量,不存在競爭關係。

public void lockElimination(){
    Object o = new Object();
    synchronized (o) {
        System.out.println("Say Hello!");
    }
}

public void lockEliminationExt(){
    Object o = new Object();
    System.out.println("Sqy Hello!");
}

鎖粗化

鎖粗化溼 虛擬機對鎖的另一種優化處理。通過擴大鎖的範圍,避免反覆加鎖和釋放鎖。比如下面兩個方法經過鎖粗化優化之後執行效率一樣了。

public void lockCoarse(){
    for (int i = 0; i < 10000; i++) {
        synchronized (SynchronizedDemo.class) {
            System.out.println("Say Hello!");
        }
    }
}

public void lockCoarseExt(){
    synchronized (SynchronizedDemo.class) {
        for (int i = 0; i < 10000; i++) {
            System.out.println("Say Hello!");
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章