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
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++指針)
