Sychronized 深度解析

synchronized可修飾普通方法、靜態方法和代碼塊 。

修飾普通方法,鎖的是對象,一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法。

public synchronized void sayHello(){
}

修飾靜態方法,鎖的是類(new多個對象都是源於同一個類,同步靜態方法仍然互鎖)

此類所有的實例對象,都無法同時訪問類中的所有同步靜態(synchronized static)方法

public synchronized static void sayHello(){
}

修飾代碼塊,取決於鎖的是什麼(synchronized(this)    synchronized(My.class) )

synchronized(this){
}

 

1 原理

synchronized是基於JVM層面的,lock是基於JDK層面的

  • 代碼塊同步原理
package com.paddx.test.concurrent;

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

javap  進行class文件反編譯

每個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

1、如果monitor的進入數爲0,則該線程進入monitor,然後將進入數設置爲1,該線程即爲monitor的所有者。

2、如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.

3、如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再重新嘗試獲取monitor的所有權。

執行monitorexit的線程必須是objectref所對應的monitor的所有者。

指令執行時,monitor的進入數減1,如果減1後進入數爲0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程都可以嘗試去獲取這個 monitor 的所有權,所以是非公平鎖。 

  通過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是爲什麼只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。

  • 同步方法原理
package com.paddx.test.concurrent;

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}

2 幾種鎖類型

synchronized按鎖的量級從輕到重分爲:無鎖--->  偏向鎖--->輕量鎖---->重量鎖       鎖只能升級不能降級

偏向鎖:如果目前只有一個線程多次獲得鎖,就沒必要下次再用鎖的時候重新競爭,輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,下次還是這個線程來用對象就可以無需驗證直接獲得鎖。如果有多個線程搶用鎖,則撤銷偏向鎖並膨脹爲輕量鎖。   無阻塞   無自旋    加鎖解鎖無消耗  適用於無多線程執行同步代碼的情況

java ååé - silver9886@126 - silver9886@126çå客

輕量鎖:競爭鎖的線程首先需要拷貝對象頭中的Mark Word到幀棧的鎖記錄中。拷貝成功後使用CAS操作嘗試將對象的Mark Word更新爲指向當前線程的指針。如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖。如果更新失敗,那麼意味着有多個線程在競爭。
當競爭線程嘗試佔用輕量級鎖失敗多次之後(使用自旋)輕量級鎖就會膨脹爲重量級鎖,重量級線程指針指向競爭線程,競爭線程也會阻塞,等待輕量級線程釋放鎖後喚醒他。     有自旋   無阻塞    佔用CPU   適用同步代碼執行快的情況

重量鎖:競爭失敗後,線程阻塞,釋放鎖後,喚醒阻塞的線程,不使用自旋鎖,不會那麼消耗CPU,所以重量級鎖適合用在同步塊執行時間長的情況下。     有阻塞      無自旋

 

還有其他幾種鎖類型作爲延伸閱讀。

互斥鎖mutex,用於保證在任何時刻,都只能有一個線程訪問該對象。當獲取鎖操作失敗時,線程會進入睡眠,等待鎖釋放時被喚醒 

自旋鎖spinlock,在任何時刻同樣只能有一個線程訪問對象。但是當獲取鎖操作失敗時,不會進入睡眠,而是會在原地自旋,直到鎖被釋放。這樣節省了線程從睡眠狀態到被喚醒期間的消耗,在加鎖時間短暫的環境下會極大的提高效率。線程不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取鎖,雖然CPU的時間被消耗了,但是比線程下文切換時間要少。這個時候使用自旋是划算的。

讀寫鎖:rwlock,區分讀和寫,處於讀操作時,可以允許多個線程同時獲得讀操作。但是同一時刻只能有一個線程可以獲得寫鎖。其它獲取寫鎖失敗的線程都會進入睡眠狀態,直到寫鎖釋放時被喚醒。 
注意:寫鎖會阻塞其它讀寫鎖。當有一個線程獲得寫鎖在寫時,讀鎖也不能被其它線程獲取;寫優先於讀,當有線程因爲等待寫鎖而進入睡眠時,則後續讀者也必須等待 
適用於讀取數據的頻率遠遠大於寫數據的頻率的場合。 

3 synchronized搶佔鎖實例

3.1 synchronized(this)   

public class Synchronized {

    public  void method1(){
        synchronized (this) {
            System.out.println("method1 start");
            try {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            } catch (Exception e) {

            }
            System.out.println("method1 end");
        }
    }

    public  void method2(){
        synchronized (this) {
            System.out.println("method2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            } catch (Exception e) {

            }
            System.out.println("method2 end");
        }
    }

    public static void main(String[] args) {
        System.out.println("程序運行");
        Synchronized syn = new Synchronized();
        // Synchronized syn2 = new Synchronized();
        new Thread(()->{
            syn.method1();
        }).start();
        new Thread(()->{
            syn.method2();
        }).start();

    }
}

結果自然是先執行method1,再執行method2

程序運行
method1 start
Method 1 execute
method1 end
method2 start
Method 2 execute
method2 end

3.2 synchronized(*.class)

public class Synchronized {

    public  void method1(){
        synchronized (Synchronized.class) {
            System.out.println("method1 start");
            try {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            } catch (Exception e) {

            }
            System.out.println("method1 end");
        }
    }

    public  void method2(){
        synchronized (Synchronized.class) {
            System.out.println("method2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            } catch (Exception e) {

            }
            System.out.println("method2 end");
        }
    }

    public static void main(String[] args) {
        System.out.println("程序運行");
        Synchronized syn = new Synchronized();
        Synchronized syn2 = new Synchronized();
        new Thread(()->{
            syn.method1();
        }).start();
        new Thread(()->{
            syn2.method2();
        }).start();

    }
}

鎖的是類,那麼該類生成的所有實例對象都會被鎖住。

程序運行
method1 start
Method 1 execute
method1 end
method2 start
Method 2 execute
method2 end

3.3 synchronized(*.class)      synchronized(this)

public class Synchronized {

    public  void method1(){
        synchronized (Synchronized.class) {
            System.out.println("method1 start");
            try {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            } catch (Exception e) {

            }
            System.out.println("method1 end");
        }
    }

    public  void method2(){
        synchronized (this) {
            System.out.println("method2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            } catch (Exception e) {

            }
            System.out.println("method2 end");
        }
    }

    public static void main(String[] args) {
        System.out.println("程序運行");
        Synchronized syn = new Synchronized();
        // Synchronized syn2 = new Synchronized();
        new Thread(()->{
            syn.method1();
        }).start();
        new Thread(()->{
            syn.method2();
        }).start();

    }
}

一個鎖的是 .class文件,一個鎖的是對象,那麼到底這兩個方法會互斥執行麼?

答案是不會,因爲本質上鎖的就不是同一個對象!!!

程序運行
method1 start
Method 1 execute
method2 start
Method 2 execute
method2 end
method1 end

3.4 synchronized  static 方法

public class Synchronized {

    public  synchronized static void method1(){

        System.out.println("method1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (Exception e) {

        }
        System.out.println("method1 end");

    }

    public  synchronized static void method2(){
            System.out.println("method2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            } catch (Exception e) {

            }
            System.out.println("method2 end");
    }

    public static void main(String[] args) {
        System.out.println("程序運行");
        Synchronized syn = new Synchronized();
        Synchronized syn2 = new Synchronized();
        new Thread(()->{
            syn.method1();
        }).start();
        new Thread(()->{
            syn2.method2();
        }).start();

    }
}

鎖加在靜態方法上,鎖的是class對象,那麼new 的新對象自然也會搶佔鎖

3.5 synchronized static  和 synchronized 混合使用

public class Synchronized {

    public  synchronized static void method1(){

        System.out.println("method1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (Exception e) {

        }
        System.out.println("method1 end");

    }

    public  synchronized   void method2(){
            System.out.println("method2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            } catch (Exception e) {

            }
            System.out.println("method2 end");
    }

    public static void main(String[] args) {
        System.out.println("程序運行");
        Synchronized syn = new Synchronized();
        Synchronized syn2 = new Synchronized();
        new Thread(()->{
            syn.method1();
        }).start();
        new Thread(()->{
            syn2.method2();
        }).start();

    }
}

一個鎖的class,一個鎖的方法,自然不會發生互斥地現象。synchronized加在static方法上,只會鎖其他synchronized static方法

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章