分析:這種情況是經典的對象鎖中的方法鎖,兩個線程爭奪同壹個對象鎖,所以會相互等待,是線程安全的。
兩個線程同時訪問同壹個對象的同步方法,是線程安全的。
這種場景就是對象鎖失效的場景,原因出在訪問的是兩個對象的同步方法,那麽這兩個線程分別持有的兩個線程的鎖,所以是互相不會受限的。加鎖的目的是為了讓多個線程競爭同壹把鎖,而這種情況多個線程之間不再競爭同壹把鎖,而是分別持有壹把鎖,所以我們的結論是:
兩個線程同時訪問兩個對象的同步方法,是線程不安全的。
運行結果:
兩個線程是並行執行的,所以線程不安全。
線程名:Thread-0,運行開始
線程名:Thread-1,運行開始
線程:Thread-0,運行結束
線程:Thread-1,運行結束
測試結束
兩個線程(thread1、thread2),訪問兩個對象(instance1、instance2)的同步方法(method()),兩個線程都有各自的鎖,不能形成兩個線程競爭壹把鎖的局勢,所以這時,synchronized修飾的方法method()和不用synchronized修飾的效果壹樣(不信去把synchronized關鍵字去掉,運行結果壹樣),所以此時的method()只是個普通方法。
若要使鎖生效,只需將method()方法用static修飾,這樣就形成了類鎖,多個實例(instance1、instance2)***同競爭壹把類鎖,就可以使兩個線程串行執行了。這也就是下壹個場景要講的內容。
這個場景解決的是場景二中出現的線程不安全問題,即用類鎖實現:
兩個線程同時訪問(壹個或兩個)對象的靜態同步方法,是線程安全的。
這個場景是兩個線程其中壹個訪問同步方法,另壹個訪問非同步方法,此時程序會不會串行執行呢,也就是說是不是線程安全的呢?
我們可以確定是線程不安全的,如果方法不加synchronized都是安全的,那就不需要同步方法了。驗證下我們的結論:
兩個線程分別同時訪問(壹個或兩個)對象的同步方法和非同步方法,是線程不安全的。
兩個線程是並行執行的,所以是線程不安去的。
線程名:Thread-0,同步方法,運行開始
線程名:Thread-1,普通方法,運行開始
線程:Thread-0,同步方法,運行結束
線程:Thread-1,普通方法,運行結束
測試結束
問題在於此:method1沒有被synchronized修飾,所以不會受到鎖的影響。即便是在同壹個對象中,當然在多個實例中,更不會被鎖影響了。結論:
非同步方法不受其它由synchronized修飾的同步方法影響
妳可能想到壹個類似場景:多個線程訪問同壹個對象中的同步方法,同步方法又調用壹個非同步方法,這個場景會是線程安全的嗎?
我們來實驗下這個場景,用兩個線程調用同步方法,在同步方法中調用普通方法;再用壹個線程直接調用普通方法,看看是否是線程安全的?
線程名:Thread-0,普通方法,運行開始
線程名:Thread-1,同步方法,運行開始
線程:Thread-1,同步方法,運行結束,開始調用普通方法
線程名:Thread-1,普通方法,運行開始
線程:Thread-0,普通方法,運行結束
線程:Thread-1,普通方法,運行結束
線程名:Thread-2,同步方法,運行開始
線程:Thread-2,同步方法,運行結束,開始調用普通方法
線程名:Thread-2,普通方法,運行開始
線程:Thread-2,普通方法,運行結束
測試結束
我們可以看出,普通方法被兩個線程並行執行,不是線程安全的。這是為什麽呢?
因為如果非同步方法,有任何其他線程直接調用,而不是僅在調用同步方法時,才調用非同步方法,此時會出現多個線程並行執行非同步方法的情況,線程就不安全了。
對於同步方法中調用非同步方法時,要想保證線程安全,就必須保證非同步方法的入口,僅出現在同步方法中。但這種控制方式不夠優雅,若被不明情況的人直接調用非同步方法,就會導致原有的線程同步不再安全。所以不推薦大家在項目中這樣使用,但我們要理解這種情況,並且我們要用語義明確的、讓人壹看就知道這是同步方法的方式,來處理線程安全的問題。
所以,最簡單的方式,是在非同步方法上,也加上synchronized關鍵字,使其變成壹個同步方法,這樣就變成了《場景五:兩個線程同時訪問同壹個對象的不同的同步方法》,這種場景下,大家就很清楚的看到,同壹個對象中的兩個同步方法,不管哪個線程調用,都是線程安全的了。
兩個線程訪問同壹個對象中的同步方法,同步方法又調用壹個非同步方法,僅在沒有其他線程直接調用非同步方法的情況下,是線程安全的。若有其他線程直接調用非同步方法,則是線程不安全的。
這個場景也是在探討對象鎖的作用範圍,對象鎖的作用範圍是對象中的所有同步方法。所以,當訪問同壹個對象中的多個同步方法時,結論是:
兩個線程同時訪問同壹個對象的不同的同步方法時,是線程安全的。
是線程安全的。
線程名:Thread-1,同步方法1,運行開始
線程:Thread-1,同步方法1,運行結束
線程名:Thread-0,同步方法0,運行開始
線程:Thread-0,同步方法0,運行結束
測試結束
兩個方法(method0()和method1())的synchronized修飾符,雖沒有指定鎖對象,但默認鎖對象為this對象為鎖對象,
所以對於同壹個實例(instance),兩個線程拿到的鎖是同壹把鎖,此時同步方法會串行執行。這也是synchronized關鍵字的可重入性的壹種體現。
線程名:Thread-0,靜態同步方法0,運行開始
線程名:Thread-1,非靜態同步方法1,運行開始
線程:Thread-1,非靜態同步方法1,運行結束
線程:Thread-0,靜態同步方法0,運行結束
測試結束
本場景探討的是synchronized釋放鎖的場景:
只有當同步方法執行完或執行時拋出異常這兩種情況,才會釋放鎖。
所以,在壹個線程的同步方法中出現異常的時候,會釋放鎖,另壹個線程得到鎖,繼續執行。而不會出現壹個線程拋出異常後,另壹個線程壹直等待獲取鎖的情況。這是因為JVM在同步方法拋出異常的時候,會自動釋放鎖對象。
線程名:Thread-0,運行開始
線程名:Thread-0,拋出異常,釋放鎖
線程名:Thread-1,運行開始
Exception in thread "Thread-0" java.lang.RuntimeException
at com.study.synchronize.conditions.Condition7.method0(Condition7.java:34)
at com.study.synchronize.conditions.Condition7.run(Condition7.java:17)
at java.lang.Thread.run(Thread.java:748)
線程:Thread-1,運行結束
測試結束
可以看出線程還是串行執行的,說明是線程安全的。而且出現異常後,不會造成死鎖現象,JVM會自動釋放出現異常線程的鎖對象,其他線程獲取鎖繼續執行。