Synchronized和Lock的實現原理和鎖升級


Synchronized底層實現

1)先在Idea下載一個ByteCode插件來觀察java經過編譯之后的字節碼

public class TestSync {
    synchronized void m() {

    }

    void n() {
        synchronized (this) {//monitorenter 

        } //monitorexit
    }

    public static void main(String[] args) {

    }
}

然后idea—view—showByteCode

這是我們n方法的字節碼 為synchronized關鍵字會在同步塊前后增加monitorenter monitorexit指令

在虛擬機規范對monitorenter和monitorexit的行為描述中,有兩點需要注意。

首先synchronized同步塊對同一線程來說是可重入的,不會出現自己把自己鎖死的問題

其次,同步塊在已進入的線程執行完成之前,會阻塞后面其他線程的進入

還有:方法及的同步是隱式的,即無須通過字節碼指令來控制,它實現在方法的調用和返回操作之中。虛擬機可以從方法常量池的方法表結構中的ACC_SYNCHRONIZED訪問標志得知一個方法是否聲明為同步方法。

2)JVM層面

所謂給對象上鎖,就是對象頭上產生了變化,鎖信息就是存在MarkWord上面,加鎖就是修改MarkWord

用JOL工具觀察內存布局:(JOL就是maven里面的一個jar包 可以用來觀察java內存的布局和大小)

觀察Synchronized鎖升級的過程,只需要觀察對象MarkWord的變化就行(最后兩個字節是鎖標志位)

C C++調用了操作系統提供的同步機制

3)OS和硬件層面

X86 : lock cmpxchg / xxx 實現CAS操作的最終指令  (lock后面的指令執行的過程中 區域被lock鎖定,只有我這個指令能執行)

https://blog.csdn.net/21aspnet/article/details/88571740

總結:synchronized是基於jvm底層實現的數據同步 加鎖解鎖過程由JVM自動控制,

Synchronized鎖升級的過程

先說下什么是重量級鎖:JDK早其sysnchronized叫是重量級鎖,申請資源必須通過kernel,需要從用戶空間切換到內核空間(從用戶態向內核態調用)拿到鎖,然后把狀態返回給用戶空間—驚動操作系統老大

    用戶空間做一些比較關鍵的事情 需要通過老大(OS)來做,讀寫網絡,寫硬盤,比較敏感的操作必須通過操作系統進行,可以保證操作系統比較健壯!

偏向鎖和自旋鎖都不需要驚動操作系統老大。

重量級鎖:JDK早其sysnchronized叫是重量級鎖,申請資源必須通過kernel,需要從用戶空間切換到內核空間(從用戶態向內核態調用)—驚動操作系統老大

     當競爭的線程特別多時,自旋鎖就不適用了(一個線程運行,剩下的都在自旋) 

     重量鎖:其他線程都進隊列等着(等待隊列),不需要在那里轉,占用CPU資源了

自旋鎖(輕量級鎖):當多個線程競爭時(競爭的線程不多),以CAS的方式修改MarkWord 誰修改成功了就算誰的

          類似數據庫的樂觀鎖(樂觀鎖有版本號,而自旋鎖是比較操作的值,存在ABA問題)

         指向線程棧中的LockRecord,記錄了線程被鎖住多少次(Syn是可重入鎖)

偏向鎖:偏向鎖是有偏向的,偏向於某個線程,不需要驚動操作系統,把字節的線程Id記到MarkWord里面

         在JDK類庫中,大多數只在一個線程里面運行(比如StringBuffer)為了一個線程還要驚動操作系統;比較浪費

    偏向鎖連CAS都不做了(消除數據在無競爭情況下的同步原語)

    凡是有人第一次得到這把鎖的時候(把線程的Id放到MarkWord里面),

自旋鎖什么時候升級成重量級鎖

JDK1.6之前:在某一個線程自旋次數超過十次就會升級成重量級鎖

JDK1.6之后:自適應自旋,JDK根據線程運行情況自己判斷

鎖升級的過程

普通對象和匿名偏向的區別:因為JVM啟動4s之后才會啟動偏向鎖,(利用4s鍾的時間判斷 需不需要啟動偏向鎖,如果JVM能確定會有多個線程爭搶某些對象 則不需要啟動偏向鎖)

  所以在程序前4s new出來的對象是普通對象

  4s之后new出現的對象是匿名偏向(沒有偏向任何人)

    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        String s = ClassLayout.parseInstance(o).toPrintable();
        System.out.println(s);
        TimeUnit.SECONDS.sleep(5);
        Object o2 = new Object();
        String s2 = ClassLayout.parseInstance(o2).toPrintable();
        System.out.println(s2);
    }

這是測試程序,觀察最后兩位字節

java.lang.Object object internals:
 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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object object internals:
 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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
View Code

Synchronize可重入鎖

可重入鎖的意思是我鎖了一個對象之后,該線程又申請了一把鎖,發現當前持有這把鎖的就是我自己這個線程,然后就繼續執行就可以了

每個線程,想上自旋鎖的過程中,會在線程棧里面生成一個LR對象和鎖住的對象關聯(Lock Record 鎖記錄),往對象MarkWord設的是鎖記錄(LR)的指針,

第二個線程持有這把鎖了 再加Synchronize的過程中,會在線程里再次生成一個Lock Record,解鎖一次 一個Lock Record彈出就行了

Synchronize必須是可重入的,不然子類實現父類沒有辦法實現。

Lock的底層實現原理AQS

synchronized是基於jvm底層實現的數據同步 加鎖解鎖過程由JVM自動控制,lock是基於Java編寫,主要通過硬件依賴CPU指令實現數據同步,與底層的JVM無關。

java.util.concurrent.locks包中有很多Lock的實現類,常用的有ReenTrantLock、ReadWriteLock

其實現都依賴java.util.concurrent.AbstractQueuedSynchronizer類(簡稱AQS),實現思路都大同小異,因此我們以ReentrantLock作為講解切入點。

 AQS的核心是一個volatile修飾的state以及監控這個state的雙向鏈表,鏈表的節點里面裝的是線程Thread,當一個Node拿到這把鎖 也就是拿到這個state,並且改了值之后(以CAS的方式從0改到1),說明里面的線程持有這把鎖

lock的Lock方法會調用acquire(int arg)去獲得鎖

        final void lock() {
            acquire(1);
        }
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //如果得不到這把鎖 就跑隊列里面等着
            selfInterrupt();
    }

以下是tryAcquire(arg)的實現

 /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) { //如果state等於0,用CAS的方式
                    setExclusiveOwnerThread(current); //把當前線程設為獨占這個state的線程,說明得到了這把鎖(這把鎖是互斥的 別人在來的時候 看到是1)
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//如果當前線程就是獨占state的線程
                int nextc = c + acquires;   //直接相加 表示可重入
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 以下是AQS的類圖

所謂CAS就是 Compare And Set 

cas(V,Expected,NewValue) 當前線程想改V這個值的 期望值(當前線程認為你原來應該有的值) 

if(V=E) V=New otherwise try again or fail

CAS的操作是CPU的原語支持(Unsafe=C C++指針)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM