java多線程4:synchronized關鍵字

概述

  java有各類各樣的鎖,而且每種鎖的特性不一樣,合理場景下利用鎖能夠展示出很是高的效率。synchronized內置鎖就是Java的一種重量級鎖,它可以解決併發編程中出現多個線程同時訪問一個共享,可變的臨界資源時出現的線程安全問題。讓多個線程序列化訪問臨界資源,同一時刻,只能有一個線程訪問臨界資源,同步互斥,這樣就保證了操做的原子性。html

synchronized使用

同步方法塊java

public class ThreadDemo5 implements Runnable{

    private int count = 0;

    @Override
    public void run() {
        synchronized (this){
            for (int i = 0; i < 10; ++i){
                count++;
                System.out.println("執行的線程是=>" + Thread.currentThread().getName() + "執行結果爲->" + count);
            }
        }

    }

    public static void main(String[] args) {
        ThreadDemo5 threadDemo5 = new ThreadDemo5();
        Thread thread1 = new Thread(threadDemo5,"thread1");
        Thread thread2 = new Thread(threadDemo5,"thread2");
        thread1.start();
        thread2.start();
    }
}

執行結果編程

執行的線程是=>thread1執行結果爲->1
執行的線程是=>thread1執行結果爲->2
執行的線程是=>thread1執行結果爲->3
執行的線程是=>thread1執行結果爲->4
執行的線程是=>thread1執行結果爲->5
執行的線程是=>thread1執行結果爲->6
執行的線程是=>thread1執行結果爲->7
執行的線程是=>thread1執行結果爲->8
執行的線程是=>thread1執行結果爲->9
執行的線程是=>thread1執行結果爲->10
執行的線程是=>thread2執行結果爲->11
執行的線程是=>thread2執行結果爲->12
執行的線程是=>thread2執行結果爲->13
執行的線程是=>thread2執行結果爲->14
執行的線程是=>thread2執行結果爲->15
執行的線程是=>thread2執行結果爲->16
執行的線程是=>thread2執行結果爲->17
執行的線程是=>thread2執行結果爲->18
執行的線程是=>thread2執行結果爲->19
執行的線程是=>thread2執行結果爲->20

 同步方法塊,synchronized鎖的是括號裏的對象,每一個線程要進入代碼塊前必須先獲取對象的的鎖,纔可執行。synchronized是一個隱式鎖,也是jvm內置的鎖,它會自動加鎖和解鎖,同時java的每一個對象均可以做爲鎖。數組

普通同步方法安全

public class ThreadDemo6 implements Runnable {

    private int count = 0;

    @Override
    public void run() {
        say();
    }

    private synchronized void say(){
        for (int i = 0; i < 10; ++i){
            count++;
            System.out.println("如今執行的線程執行=>" + Thread.currentThread().getName() + "結果爲->" + count);
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ThreadDemo6 threadDemo6 = new ThreadDemo6();
        Thread thread1 = new Thread(threadDemo6,"Thread-1");
        Thread thread2 = new Thread(threadDemo6,"Thread-2");
        thread1.start();
        thread2.start();
    }
}

執行結果bash

如今執行的線程執行=>Thread-1結果爲->1
如今執行的線程執行=>Thread-1結果爲->2
如今執行的線程執行=>Thread-1結果爲->3
如今執行的線程執行=>Thread-1結果爲->4
如今執行的線程執行=>Thread-1結果爲->5
如今執行的線程執行=>Thread-1結果爲->6
如今執行的線程執行=>Thread-1結果爲->7
如今執行的線程執行=>Thread-1結果爲->8
如今執行的線程執行=>Thread-1結果爲->9
如今執行的線程執行=>Thread-1結果爲->10
/*停頓5秒*/ 如今執行的線程執行=>Thread-2結果爲->11 如今執行的線程執行=>Thread-2結果爲->12 如今執行的線程執行=>Thread-2結果爲->13 如今執行的線程執行=>Thread-2結果爲->14 如今執行的線程執行=>Thread-2結果爲->15 如今執行的線程執行=>Thread-2結果爲->16 如今執行的線程執行=>Thread-2結果爲->17 如今執行的線程執行=>Thread-2結果爲->18 如今執行的線程執行=>Thread-2結果爲->19 如今執行的線程執行=>Thread-2結果爲->20

普通同步方法,經過例子能夠知道他是一個對象鎖,線程1未釋放鎖,線程2只能被動等待,改下代碼多線程

 public static void main(String[] args) {
        Thread thread1 = new Thread(new ThreadDemo6(),"Thread-1");
        Thread thread2 = new Thread(new ThreadDemo6(),"Thread-2");
        thread1.start();
        thread2.start();
    }

執行結果併發

如今執行的線程執行=>Thread-2結果爲->1
如今執行的線程執行=>Thread-2結果爲->2
如今執行的線程執行=>Thread-2結果爲->3
如今執行的線程執行=>Thread-2結果爲->4
如今執行的線程執行=>Thread-2結果爲->5
如今執行的線程執行=>Thread-2結果爲->6
如今執行的線程執行=>Thread-2結果爲->7
如今執行的線程執行=>Thread-2結果爲->8
如今執行的線程執行=>Thread-2結果爲->9
如今執行的線程執行=>Thread-2結果爲->10
如今執行的線程執行=>Thread-1結果爲->1
如今執行的線程執行=>Thread-1結果爲->2
如今執行的線程執行=>Thread-1結果爲->3
如今執行的線程執行=>Thread-1結果爲->4
如今執行的線程執行=>Thread-1結果爲->5
如今執行的線程執行=>Thread-1結果爲->6
如今執行的線程執行=>Thread-1結果爲->7
如今執行的線程執行=>Thread-1結果爲->8
如今執行的線程執行=>Thread-1結果爲->9
如今執行的線程執行=>Thread-1結果爲->10
停頓。。

不是同一個對象鎖,因此線程1和線程2不存在鎖的互斥,而且不存在共享資源count變量,因此多個線程訪問的必須是同一個對象,鎖纔會變得有意義。jvm

靜態同步方法ide

public class ThreadDemo6 implements Runnable {

    private static int count = 0;

    @Override
    public void run() {
        say();
    }

    private static synchronized void say(){
        for (int i = 0; i < 10; ++i){
            count++;
            System.out.println("如今執行的線程執行=>" + Thread.currentThread().getName() + "結果爲->" + count);
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        Thread thread1 = new Thread(new ThreadDemo6(),"Thread-1");
        Thread thread2 = new Thread(new ThreadDemo6(),"Thread-2");
        thread1.start();
        thread2.start();
    }
}

執行結果

如今執行的線程執行=>Thread-1結果爲->1
如今執行的線程執行=>Thread-1結果爲->2
如今執行的線程執行=>Thread-1結果爲->3
如今執行的線程執行=>Thread-1結果爲->4
如今執行的線程執行=>Thread-1結果爲->5
如今執行的線程執行=>Thread-1結果爲->6
如今執行的線程執行=>Thread-1結果爲->7
如今執行的線程執行=>Thread-1結果爲->8
如今執行的線程執行=>Thread-1結果爲->9
如今執行的線程執行=>Thread-1結果爲->10
/*停頓*/
如今執行的線程執行=>Thread-2結果爲->11
如今執行的線程執行=>Thread-2結果爲->12
如今執行的線程執行=>Thread-2結果爲->13
如今執行的線程執行=>Thread-2結果爲->14
如今執行的線程執行=>Thread-2結果爲->15
如今執行的線程執行=>Thread-2結果爲->16
如今執行的線程執行=>Thread-2結果爲->17
如今執行的線程執行=>Thread-2結果爲->18
如今執行的線程執行=>Thread-2結果爲->19
如今執行的線程執行=>Thread-2結果爲->20

即便他們是不一樣的對象,但執行的都是一個類的方法,在執行同步靜態方法時,爭搶的是類鎖,這也是和非靜態同步方法所區別開來。由於他們是兩個不一樣的鎖,一個是對象鎖,一個是類鎖。因此,在代碼中,一個線程能夠同時搶有對象鎖,類鎖。

 monitor和monitorexit

Java的互斥鎖是如何的實現的,javap -verbose ThreadDemo3.class 看下字節碼子令。

同步代碼塊

   

 非靜態方法同步

 

 

 靜態方法同步

 

 同步塊中monitor被佔用就處於鎖定狀態,其餘本次搶鎖失敗的線程將會放入Wait Set等待同步隊列中進行等待,佔用鎖的線程執行完同步塊而且釋放鎖後將會通知放入同步隊列的的其餘線程,通知他們,我釋放鎖了趕忙來搶吧!而相對於普通的靜態同步方法和非靜態同步方法,常量池匯中多了ACC_SYNCHRONIZED標記,方法調用就會去檢查是否是有這個標記若是有,jvm就會要求線程在調用前先請求鎖,但不管哪一種實現,在實質上仍是經過對象相關聯的的monitor獲取的。

而monitor是什麼哪?它是每一個對象建立以後都會在jvm內部維護一個與之對應Monitor(監視器鎖)也有人叫管程反正都是一個東西,能夠理解爲每一個對象天生都有一把看不見的鎖咱們叫他monitor鎖,而每一個線程會有一個可用的MR(Monitor Record)列表,還有一個全局可用列表,每個被鎖住的對象都會和一個MR相關聯,而且對象monitor中會有一個owner字段存放佔用該鎖線程惟一標識,表示這個鎖已經被哪一個線程佔用,synchronized就是基於進入與退出Monitor對象實現方法與代碼塊同步,而監視器鎖的實現哪是依賴底層操做系統Mutex lock(互斥鎖),它是一個重量級鎖,每次從用戶態切換到內的態的資源消耗是比較大的,也所以從jdk1.6後,java對synchronized進行了優化,從一開始的無鎖狀態->偏向鎖狀態->輕量級鎖狀態->重量級鎖狀態,而且這個狀態是不可逆的。

jvm加鎖過程

 

 

 對象內存結構

 上文說過每一個Java對象都是天生的鎖,存放在Java的對象頭中,對象頭包含三個區域,對象頭,實例數據,補齊填充

第一部分是存儲對象自身運行時的數據,哈希碼,GC,偏向時間戳,保存對象的分代年齡,鎖狀態標誌,偏向鎖線程id,線程持有的鎖,若是是數組還須要一塊區域存放數組大小,class的對象指針是虛擬機經過他肯定這個對象是哪一個類的實例,咱們平時getClass直接獲取類就跟這個有關,官方稱這部分爲Mark Word,第二部分略過,第三部分規定對象的大小必須是8字節的整數倍,至於爲何,lz沒去深究暫時不知道。咱們重點關注是Mark Word的鎖標誌位,因此鎖的狀態是保存在對象頭中的,至於偏向狀態,篇幅有限,下節在談。

鎖的粗化和消除

鎖的粗化

鎖帶來性能開銷是很大的,爲了保證多線程的併發操做,一般會要求每一個線程持有鎖的時間越短越好,但若是遇到一連串對同一把鎖進行請求和釋放的操做,jvm會進行優化智能的把鎖操做的整合成一個較大同步塊,從而減小了對鎖的頻繁申請和釋放提升性能。

public class ThreadDemo7 implements Runnable {
    public void test(){
        synchronized (this){
            System.out.println(1111);
        }
        synchronized (this){
            System.out.println(222);
        }
        synchronized (this){
            System.out.println(333);
        }
    }
    public static void main(String[] args) {
        ThreadDemo7 threadDemo7 = new ThreadDemo7();
        Thread thread = new Thread(threadDemo7);
        thread.start();
    }

    @Override
    public void run() {
        test();
    }
}

鎖的消除

咱們設置了同步塊,在字節碼中也發現了monitorenter和monitorexit,至少看上去有鎖的獲取和釋放過程,但執行的結果與咱們預測的風馬牛不相及。

public class ThreadDemo8 implements Runnable {
    private static int count = 0;
    @Override
    public void run() {
        synchronized (new Object()){
            count++;
            System.out.println("鎖的消除...=>"  + Thread.currentThread().getName() + "值=>" + count);
        }
    }

    public static void main(String[] args) {
        ThreadDemo8 threadDemo8 = new ThreadDemo8();
        for (int i = 0; i < 10; ++i){
            Thread thread = new Thread(threadDemo8);
            thread.start();
        }
    }

}

執行結果

鎖的消除...=>Thread-6值=>4
鎖的消除...=>Thread-4值=>2
鎖的消除...=>Thread-5值=>4
鎖的消除...=>Thread-0值=>4
鎖的消除...=>Thread-1值=>6
鎖的消除...=>Thread-2值=>6
鎖的消除...=>Thread-3值=>7
鎖的消除...=>Thread-9值=>8
鎖的消除...=>Thread-7值=>9
鎖的消除...=>Thread-8值=>10

這是由於jit在編譯代碼時,使用了逃逸分析的技術,判斷程序中的使用鎖的對象是否被其餘線程使用,若是隻被一個線程使用,這個同步代碼就不會生成synchronized鎖標識的鎖申請和釋放的機器碼,消除了鎖的使用流程。因此,並非全部的實例對象都存放在堆區,若是發生線程逃逸行爲,將會存儲在線程棧上。

總結

鎖的重入和鎖膨脹升級,在後期在慢慢整理。

參考

https://blog.csdn.net/axiaoboge/article/details/84335452

https://www.cnblogs.com/xrq730/p/4853578.html

相關文章
相關標籤/搜索