關鍵字synchronized的使用

爲了解決非線程安全問題,就需要使用線程同步。實現線程同步的一種方式就是使用synchronized關鍵字。

1.synchronized的用法

(1)synchronized可以修飾方法,表示這個方法在任意時刻只能由一個線程訪問。

(2)synchronized用在類聲明中,表明該類中的所有方法都是synchronized的。

(3)synchronized還可以用在一段代碼中,被synchronized關鍵字修飾的代碼就叫做同步語句塊。

Java引入了對象互斥鎖的概念,來保證數據共享操作的完整性。Java中每個對象都對應一個稱爲“互斥鎖”的標記,即每個對象都有屬於自己的一個鎖Lock。被synchrionized修飾的對象,方法或代碼,就相當於給他們加了鎖,加鎖的這段代碼成爲“互斥區”或“臨界區”。

被synchronized關鍵字修飾的代碼一定是排隊運行的。

下面分別介紹的這幾種用法。

2.synchronized修飾方法

被synchrionized修飾的方法也叫同步方法。synchrionized修飾的方法有幾個重要的特性:

(1)關鍵字synchrionized修飾的方法取得的鎖是對象鎖,而不是一段代碼或方法。

哪個線程先執行帶synchrionized關鍵字的代碼,哪個線程就持有該方法所屬對象的鎖Lock,如果多個線程訪問的是同一個對象,那麼其他線程就必須等待。

(2)synchrionized修飾的方法不影響非synchrionized方法的執行

如果兩個線程A和B訪問同一個對象object,如果A先持有了object對象的Lock鎖,B線程可以以異步的方式調用object對象中的非synchrionized方法。

(3)一個對象中的多個synchrionized方法要排隊執行

對象object有兩個被synchrionized關鍵字修飾的同步方法,如果A訪問object對象的一個同步方法時,先持有了object對象的Lock鎖,B線程如果這時調用object的另一個同步方法,就必須等待,也就是同步。由此也可以看出關鍵字synchrionized修飾的方法取得的鎖是對象鎖,在object的一個synchrionized方法未被線程訪問完畢時,其他的線程不能訪問object。

(4)關鍵字synchrionized具有鎖重入的功能。

鎖重入就是在使用synchrionized時,當一個線程得到一個對象鎖後,再次請求此對象鎖時可以再次得到該對象的鎖。因此在一個synchrionized方法/塊的內部調用本類的其他synchrionized方法/塊時,是永遠可以得到鎖的。

“可重入鎖”就是自己可以再次獲取自己的內部鎖。比如一條線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當其想要再次獲得這個對象的鎖的時候還是可以獲取的,如果鎖不可以重入的話,就會造成死鎖。

“可重入鎖”也支持在父子類繼承的環境中。當存在父子類繼承關係時,子類完全可以通過“可重入鎖”調用父類的同步方法。

(5)當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放。

如果一個線程在執行synchrionized方法的過程中出現異常,即使該方法沒有執行完畢,線程也會自動釋放原來佔有的對象鎖供其他線程使用。

(6)同步不具有繼承性。

子類繼承父類,父類中有一個同步方法,那麼子類重寫和調用該父類方法並不是同步執行的,必須也改成synchrionized方法才能同步執行。

(7)靜態同步synchronized方法作用的範圍是整個Class類

關鍵字synchronized還可以用在static靜態方法上,如果這樣寫,就是對當前的*.java文件對應的Class類進行持鎖。synchronized關鍵字加到static靜態方法上是給Class類上鎖,而synchronized關鍵字加到非static靜態方法上是給對象上鎖。

3.synchronized修飾代碼段

用關鍵字synchrionized修飾的方法在某些情況下是有弊端的。比如線程A調用調用同步方法執行了一個長時間的任務,那麼B線程要想使用該方法必須等待比較長的時間。在這種情況下可以使用synchrionized同步語句塊來解決。

synchrionized修飾的代碼塊也有幾個特徵:

(1)synchrionized(this)代碼塊同步執行

和synchrionized修飾方法類似,當兩個併發線程訪問同一對象object的synchrionized(this)的同步代碼塊時,一段時間內只能有一個線程被執行,另一個線程必須等待當前線程執行完該代碼以後才能接着執行該代碼塊。通常synchrionized(this)的代碼塊是在方法中,形式如:

public class Demo{
     public void method( ){
         synchronized(this){
         //同步代碼塊中的內容
         }
         //其他代碼
     }
}

 如何使用synchrionized(this)代碼塊解決上述提到的使用synchrionized方法,程序的執行效率低的問題呢?

(2)當一個線程訪問object的同步代碼塊時,另一個線程仍然可以訪問該object對象中的非synchrionized(this)同步代碼塊。

有了這個結論,那我們在執行有關對共享數據進行訪問和操作的代碼時,可以把比較耗時但不涉及共享數據訪問的操作的代碼放到非同步代碼塊中,同步代碼塊中只存放和共享數據訪問和操作的代碼,這樣就能減少多線程執行的時間,提高程序運行效率。

(3)不在synchrionized(this)的代碼塊中的就不是同步執行,在synchrionized(this)代碼塊中的就是同步執行。

根據第(2)個結論,也就很容易得出這個結論。

(4)一個對象中的多個synchrionized(this)代碼塊要排隊執行

object中有多個synchrionized(this)方法。當一個線程訪問object的一個synchrionized(this)同步代碼塊時,其他線程對同一個關鍵字修飾的object中所有其他對象synchrionized(this)同步代碼塊的訪問將被阻塞,說明synchronized使用的“對象監視器”是一個,都是object對象。

(5)synchrionized(this)鎖定當前對象。

和synchrionized方法一樣,synchrionized(this)鎖定的也是當前對象。

(6)將實例變量及方法參數作爲對象監視器

前面都是使用synchrionized(this)來同步代碼塊,其實Java還支持“任意對象”作爲“對象監視器”來實現同步的功能。這個“任意對象”大多數是實例變量及方法的參數,使用格式爲synchrionized(非this對象)。

鎖非this對象具有一定的優點:如果在一個類中有很多synchronized方法,這時雖然能實現同步,但會因爲同步受到阻塞,所以影響運行效率;但如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,不與其他鎖this同步方法爭搶this鎖,則可大大提高運行效率。

使用“synchrionized(非this對象x)”同步代碼塊格式進行同步操作時,對象監視器必須是同一個對象。如果不是同一個對象監視器,運行的結果就是異步調用,就會交叉運行。

(7)將類作爲對象監視器

同步synchrionized(class)就是將類作爲對象監視器,同步synchrionized(class)代碼塊的作用和synchrionized static方法的作用是一樣的,鎖住的都是整個對象。

(8)不使用String字符串做對象監視器

同步synchrionized(非this對象)大多數情況下不使用String字符串作爲對象的監視器,而改用其他,比如new Object( )實例化一個object對象。如果想要實現的效果是異步的,使用字符串對象作爲鎖對象,那麼字符串應該是不同的。但是現在如果有兩個線程擁有不同的string對象,兩個string對象的值卻相同,那麼這兩個對象就持有相同的鎖,所以在一個線程執行時另一個線程就不能執行了。這是因爲String常量池具有緩衝作用所帶來的效果。

另外

(1)如果一個對象有既有同步方法也有同步代碼塊,那麼兩個線程分別調用同步方法和同步代碼塊,他們之間的執行就是異步的,因爲同步方法和同步代碼塊持有的不是同一個鎖。只有持有同一個鎖的方法和代碼纔是同步執行的。

4.使用同步會引起的問題

(1)同步方法容易造成死循環

如果調用同一個對象的兩個synchronized方法,那麼它們是同步執行的。這時如果第一個正在執行的synchronized方法出現死循環,那麼第二個synchronized方法將永遠得不到運行的機會。

解決方法是可以把同步方法變爲同步塊,兩個方法中聲明不同的同步塊,並且同步塊中的對象監視器是不同的,這樣這兩個方法就是異步執行,這兩個方法的執行就不會相互影響了。

(2)多線程死鎖

當多個線程共享一個資源的時候需要進行同步,但是過多的同步可能導致死鎖。Java線程死鎖就是所有的線程都在等待其他線程持有的鎖被釋放而對自己持有的鎖又保持不放,不同的線程都在等待根本不可能釋放的鎖,最後就是沒有鎖釋放,導致所有的任務都無法繼續向前推進。只要互相等待對方釋放鎖就有可能出現死鎖。

當兩個或兩個以上的線程在執行過程中,因爭奪資源而造成了互相等待,並且若無外力作用,它們都將無法推進下去的現象稱爲系統處在死鎖狀態或系統產生了死鎖。

資源佔用是互斥的,當某個線程提出申請資源後,如果有關線程在無外力協助下,永遠分配不到必需的資源而無法繼續運行,就會發生死鎖。

產生死鎖的必要條件:

互斥條件:指線程對所分配到的資源進行排它性使用。

請求和保持條件:指線程已經保持至少一個資源,但又提出了新的資源請求。

不剝奪條件:進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

循環等待條件:指在發生死鎖時,必然存在一個線程——資源的環形鏈。

在多線程技術中,死鎖是必須避免的,因爲這會造成線程的“假死”。在設計程序時要避免雙方互相持有對方的鎖的情況。

關於snchronized這些特點和用法的更多實例,可以參考:《Java多線程編程核心技術》---高洪巖(Chapter2 2.1,2.2)的內容。

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