synchronized的底層原理?


最近更新的XX必備系列適合直接背答案,不深究,不喜勿噴。

你能說簡單說一下synchronize嗎?

可別真簡單一句話就說完了呀~

參考回答:

synchronize是java中的關鍵字,可以用來修飾實例方法、靜態方法、還有代碼塊;主要有三種作用:可以確保原子性、可見性、有序性,原子性就是能夠保證同一時刻有且只有一個線程在操作共享數據,其他線程必須等該線程處理完數據后才能進行;可見性就是當一個線程在修改共享數據時,其他線程能夠看到,保證可見性,volatile關鍵字也有這個功能;有序性就是,被synchronize鎖住后的線程相當於單線程,在單線程環境jvm的重排序是不會改變程序運行結果的,可以防止重排序對多線程的影響。

補充:我們來看一下上邊這個回答,其中有好幾個部分可以繼續延伸,這里指的延伸就是面試官可以再繼續問你的問題。

延伸一:java內存模型的三大特性,或者是說一下java內存模型,或者是synchronize跟java內存模型有什么關系嗎?

首先補充為何會問到java內存模型,因為Synchronize的三種作用其實就是java內存模型保證的,再就是這個問題可能單獨就蹦到考察java內存模型(JMM)上了。

1、什么是java內存模型:java虛擬機規范中定義了java內存模型是用來屏蔽各種硬件和操作系統間內存的差異,來實現java程序在各平台下並發一致性,再就是,java內存模型並不是真實存在的,他只是一種抽象概念,定義了線程和主內存之間的抽象關系,也就是線程之間的共享變量存儲在主內存中,每個線程都有一個私有的本地內存,本地內存存儲了該線程共享變量的副本。

2、java內存模型的三大特性:java內存模型有三大特性,原子性、可見性、有序性。

原子性:要么執行,要么不執行,主要使用互斥鎖Synchronize或者lock來保證操作的原子性;
可見性:在變量修改后將新值同步回主內存,主要有兩種實現方式,一是volatile,被volatile修飾的變量發生修改后會立即刷新到主內存;二是使用Synchronize或者lock,當一個變量unlock之前會將變量的修改刷新到主內存中;
有序性:在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序不會影響單線程的執行結果,卻會影響多線程並發執行的正確性。主要有兩種方式確保有序性:volatile 和 Synchronize 關鍵字,volatile是通過添加內存屏障的方式來禁止指令重排序,也就是重排序是不能把后面的指令放到內存屏障之前執行;Synchronize是保證同一時刻有且只有一個線程執行同步代碼,類似於串聯順序執行代碼。

延伸二:你了解先行發生原則(happens-before)嗎?

為什么會出現先行發生原則:從上邊我們也能看到,如果java內存模型中所有的有序性都要靠volatile和Synchronize來實現的話,那么是非常繁瑣的,所以j就出現這么一個《先行發生原則》,用來判斷數據是否存在競爭、線程是否安全的重要依據。

參考回答:

先行發生原則是java內存模型用來定義兩個操作之間的偏序關系。比如說A操作先發生於B操作,那么在B操作發生之前,A操作修改了內存中的共享變量,那么就會被B操作察覺到。

先行發生原則其中包含8種規則,比如:程序員次序規則,volatile變量規則,線程的啟動、中止、中斷等規則。

如果再問到能簡單介紹一下你說的這幾個規則嗎?一般不會問這么細的,了解即可。

程序員次序規則:在一個線程內,在程序前面的操作先行發生於后面的操作。

volatile變量規則:對一個 volatile 變量的寫操作先行發生於后面對這個變量的讀操作。

線程啟動規則:Thread 對象的 start() 方法調用先行發生於此線程的每一個動作。

線程加入規則:Thread 對象的結束先行發生於 join() 方法返回。

線程中斷規則:對線程 interrupt() 方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,可以通過 interrupted() 方法檢測到是否有中斷發生。

延伸三:volatile的作用,volatile跟Synchronize的區別

補充:如果上邊不答到volatile,可能會跳過這個問題,但是多吹點說不定工資高點。

參考回答:

volatile的作用:volatile關鍵字主要作用是確保可見性跟有序性,當一個共享變量被volatile修飾,如果一個線程修改了這個共享變量,那么其他線程就會立馬可知,強制刷新到主內存。

volatile跟Synchronize的區別:

  1. volatile只能作用域變量,Synchronize可作用域變量、方法、類、同步代碼塊等;
  2. volatile只能保證可見性和有序性,不能保證原子性,Synchronize三者都可以保證。
  3. volatile不會造成線程阻塞,Synchronize可能會造成線程阻塞。
  4. 在性能方面synchronized關鍵字是防止多個線程同時執行一段代碼,會影響程序執行效率,而volatile關鍵字在某些情況下性能要優於synchronized。

延伸四:你能說說你剛剛提到的重排序嗎?

你是否想過,重排序為什么會對多線程產生影響?

參考回答:

重排序是編譯器和處理器為了優化程序性能而對指令進行重新排序的一種手段。重排序可以保證最終執行的結果是與程序順序執行的結果一致,並且只會對不存在數據依賴性的指令進行重排序,重排序在單線程下對最終執行結果是沒有影響的,但是在多線程下就會存在問題。

舉個例子:

int a = 1;
int b = 2;
int c = a*b;

如上,a與c之間存在數據依賴關系,所以c不能排到A的前面,同時b與c之間也存在數據依賴關系,所以,c也不能排到B的前面,但是a與b之間是不存在數據依賴關系的,所以a與b之間是可以進行重排序的,但是無論怎么重排序都是不會影響到c的值。

但是在多線程中就不一樣了,如下代碼:

class Test{

    /** 我是變量a **/
    int a = 0;

    /** 我是用來標記變量a是否被寫入 **/
    boolean flag = false;

    /** 我是寫操作 **/
    public void writer(){
        a = 1/** 第1步 **/
        flag = true/** 第2步 **/
    }

    /** 我是讀操作 **/
    public void reader(){
        if(flag){           /** 第3步 **/
            int i = a * a;  /** 第4步 **/
             ......
        }
    } 
}

flag是一個變量,用來表示變量a是否已被寫入。這里假設有兩個線程A和B ,A線程首先執行writer()方法,隨后線程B執行reader()方法。線程B在執行操作第4步的時候,能否看到線程A在操作共享變量a的寫入呢?

答案是:在多線程的情況下,不一定能看到;

由於操作1和操作2沒有數據依賴關系,編譯器和處理器可以對這兩個操作重排序;同樣,操作3和操作4沒有數據依賴關系,編譯器和處理器也可以對這兩個操作重排序。

具體細節可以看一下這篇文章,了解一下重排序對多線程的影響:https://blog.csdn.net/zhushuai1221/article/details/51491578

你能說一下Synchronize底層原理嗎?

參考回答:

synchronized的底層原理是跟monitor有關,也就是視圖器鎖,每個對象都有一個關聯的monitor,當Synchronize獲得monitor對象的所有權后會進行兩個指令:加鎖指令monitorenter跟減鎖指令monitorexit。

monitor里面有個計數器,初始值是從0開始的。如果一個線程想要獲取monitor的所有權,就看看它的計數器是不是0,如果是0的話,那么就說明沒人獲取鎖,那么它就可以獲取鎖了,然后將計數器+1,也就是執行monitorenter加鎖指令;monitorexit減鎖指令是跟在程序執行結束和異常里的,如果不是0的話,就會陷入一個堵塞等待的過程,直到為0等待結束。

最后

博客地址:https://www.cnblogs.com/niceyoo

如果覺得這篇文章有丶東西,不放關注一下我,關注是對我最大的鼓勵~

18年專科畢業后,期間一度迷茫,最近我創建了一個公眾號用來記錄自己的成長。


免責聲明!

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



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