1. 程式人生 > >Synchronized實現原理

Synchronized實現原理

一,前言

​ Synchronized 在多執行緒環境下是不可缺少的,那麼對於Synchronized 又瞭解多少呢。下面就係統總結,而對於Synchronized的基本使用,請參看另一篇部落格。

1.1,Synchronized 作用

  • 確保執行緒互斥的訪問同步程式碼
  • 保證共享變數的修改能夠及時可見
  • 有效解決重排序問題

二,從JVM理解Synchronized

​ 首先使用JDK自帶的反編譯工具檢視Synchronized編譯後的位元組碼,開啟cmd進入到.class檔案所在檔案目錄,輸入javap -v 類名.class

​ 先看如下程式碼:

package com.mult;

public class Demo {
    private static int value = 10;
    public static void main(String[] args) {
        System.out.println(new Demo().method());
    }
    public synchronized int method() {
        synchronized (Demo.class) {
            if (value > 5) {
                return value;
            } else {
                return 0;
            }
        }
    }
}

從上圖可以看出Synchronized 是通過monitorenter和monitorexit兩個位元組碼指令實現的。在每一個物件中都會存在一個Monitor監視器,而monienter和monitorexit兩者之間是互斥關係,monienter用於獲取物件鎖,而moniexit釋放物件鎖。

​ 在JVM規範文件中有以下說明:

  • 如果 Monitor 的計數器為 0,則該執行緒進入 Monitor,然後將計數器值設定為 1,該執行緒即為 Monitor 的所有者,也就是說此時獲取到物件鎖。
  • 如果執行緒已經佔有該 Monitor,只是重新進入,則進入 Monitor 的計數器加 1。
  • 如果其他執行緒已經佔用了 Monitor,則該執行緒進入阻塞狀態,直到 Monitor 的計數器為 0,再重新嘗試獲取 Monitor 的所有權。

​ 當計數器為0時,Monitor便會釋放物件鎖,那麼其他阻塞的執行緒就可以嘗試申請獲取物件鎖。

​ 總結這裡,就要引出另一個內容,就是Synchronized是可重入鎖。

三,可重入鎖

​ 可重入鎖: 一個執行緒已經獲取到物件鎖時,其他執行緒處於阻塞狀態。但獲取到物件鎖的執行緒再次去請求自己所持有的物件鎖資源時,這種情況成為可重入鎖。

​ 請看例項程式碼:

public class Demo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Demo demo = new Demo();
                demo.method_1();
            }
        }).start();
    }
    public synchronized void method_1() {
        System.out.println(Thread.currentThread().getName()+"-->method_1....");
        method_2();
    }
    public synchronized void method_2() {
        System.out.println(Thread.currentThread().getName()+"-->method_2....");
    }
}

​ 以上程式碼中只有一個demo物件鎖,在method_1中呼叫method_2結果依然可以列印,證明Synchronized是可重入鎖。反之,如果不是可重入鎖,那麼在method_1中獲取到物件鎖,接著呼叫method_2便會產生死鎖,另外兩個方法的執行緒名稱是相同的,也可以證明該執行緒拿到的就是同一個物件鎖。

注意:當子類繼承父類時,子類也是可以通過可重入鎖呼叫父類的同步方法。

四,鎖的優化

​ 在JDK6之後,對Synchronized的實現進行了優化,引入了偏向鎖、輕量級鎖,鎖,它們之間的關係為;

無鎖->偏向鎖->輕量級鎖->重量級鎖

注意:以上級別之間的轉換是單向的,只能從低階轉向高階,反之不可。

4.1,偏向鎖

​ 在某一環境下,一個執行緒可能會多次獲得物件鎖。那麼頻繁的申請鎖釋放鎖勢必會對效能造成一定影響,因此引入偏向鎖概念。當一個執行緒頻繁獲得物件鎖時,會在物件頭中儲存鎖偏向的執行緒ID,然後當該執行緒再次申請或釋放鎖時,就不再需要做其他的同步操作,因而在一定程度上可以提高系統性能。

4.2,輕量級鎖

​ 輕量級鎖在偏向鎖的上一級,在偏向鎖不再適用的情況下,就會向上升級。當升級為輕量級鎖時,Mark Word的結構也會相應的變化。執行緒在棧幀中建立鎖記錄,接著將鎖物件中Mark Word複製到執行緒建立的所記錄中,而鎖物件中的Mark Word則被替換為指向鎖記錄的指標,完成輕量級鎖的實現。而輕量級鎖的引入是為解決在重量級鎖中,多執行緒之間的效能消耗問題。

4.3,自旋鎖

​ 自選鎖顧名思義就是“自己旋轉”。同樣在多執行緒的環境下,其中一條執行緒獲得物件鎖,而其他的執行緒則在原地迴圈等待其他執行緒釋放鎖,而不是處於執行緒阻塞狀態。這種原地迴圈等待的情況是會消耗CPU資源的,預設情況下迴圈10次。自旋鎖的使用一般是小城獲取鎖的時間較短,讓其他執行緒稍微等待一段時間進而再獲得物件鎖,比如對於同步程式碼塊的執行一般是較快的。如果執行緒迴圈時間較長,那麼作業系統便會將此執行緒掛起,避免資源的更多浪費。

​ 對於自旋的概念可能不太好理解,下面寫個小Demo。

public static void main(String[] args) {
        // 執行緒1
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"開始執行了...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"執行完畢...");
            }
        }).start();
        // 執行緒2
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"開始執行了...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"執行完畢...");
            }
        }).start();
        
        System.out.println("全部執行緒執行完畢...");
    }

​ 執行結果:

分析:以上案例本來的目的是當全部執行緒執行完畢後,再列印全部執行緒執行完畢。但是在多執行緒情況下這是無法保證的,下面進行優化。

while(Thread.activeCount() != 1){
        }
System.out.println("全部執行緒執行完畢...");

​ 重複的程式碼就不再展示,只是在最後一句列印前新增死迴圈,讓其一直判斷當前活動的執行緒是否只剩下一個,如果是則退出while迴圈。那麼while迴圈就是一直在不停迴圈的等待過程,直到活動執行緒為最後一個。

適應性自旋

是不固定自旋10次一下。它可以根據它前面執行緒的自旋情況,從而調整它的自旋,甚至是不經過自旋而直接掛起。

4.4,重量級鎖

​ 當輕量級鎖膨脹到重量級鎖之後,表示執行緒只能被掛起阻塞來等待被喚醒了,那麼這種鎖機制效率就相對比較慢,同時比較損耗系統資源。

五,總結

​ 到這裡關於Synchronized的總結就結束了,還有一種ReentrantLock鎖也是可重入鎖。

​ 以上內容均是學習總結,如有不適之處歡迎留言指正。

感謝閱讀