2010-05-03 97 views
2

從Sun的教程:Java線程 - 同步問題

同步方法能夠防止線程干擾和內存一致性錯誤一個簡單的策略:如果對象是一個多線程可見,所有讀取或寫入到對象的變量是通過同步方法完成的。 (一個重要的例外:在構造對象後無法修改的final字段可以通過非同步方法安全地讀取,一旦對象被構造)這種策略是有效的,但是可能帶來活躍性的問題,因爲我們會請參閱本課後面的內容。

Q1。上述語句是否意味着如果某個類的對象將在多個線程之間共享,那麼該類的所有實例方法(final字段的getter除外)都應該同步化,因爲實例方法處理實例變量?

回答

5

被替換爲了解併發在Java中,我推薦的寶貴Java Concurrency in Practice

爲了迴應您的具體問題,雖然同步所有方法是一種快速和骯髒的方式來實現線程安全,但它根本不能很好地擴展。考慮大量惡意的Vector類。每個方法都是同步的,並且它工作起來非常糟糕,因爲迭代仍然不是線程安全的。

0

是的,這是正確的。所有修改數據或訪問可能由不同線程修改的數據的方法都需要在同一監視器上進行同步。

簡單的方法是將方法標記爲synchronized。如果這些是長時間運行的方法,那麼您可能只想同步讀取/寫入的那些部分。在這種情況下,您將定義監視器以及wait()和notify()。

+0

你能提供一個使用wait()和notify()以及synchronized語句的例子嗎? – 2010-05-03 18:55:08

0

簡單的答案是肯定的。 如果類的一個對象將被多個線程共享,則需要同步getter和setter以防止數據不一致。 如果所有線程都有單獨的對象副本,則不需要同步方法。如果你的實例方法不僅僅是設置和獲取,你必須分析等待長時間運行的getter/setter完成的線程的威脅。

+0

除了setter和getter之外,其他實例方法也處理可共享的數據。那麼我應該同步所有訪問/寫入可共享數據的實例方法嗎? – 2010-05-03 18:57:22

3

不。它意味着同步方法是實現線程安全的一種方式,但它們不是唯一的方法,並且它們本身並不能保證在所有情況下都具有完全的安全性。

+0

可以請告訴我三件事情。首先,那些可用於線程安全的其他方式是什麼(我認爲volatile變量是其中的一個)。其次,同步方法在哪些情況下失敗?第三,你認爲我們遇到的同步有哪些問題? – 2010-05-03 18:52:11

+1

線程安全的另一種方法是不變性。請注意,如果符合以下條件,則可以將您的對象視爲不可變: 1.其所有基元都是最終的 2.其所有非基元都是不可變的。 另外重要的是要明白,它並不容易實現真正的線程安全。當每個單獨的操作可能是線程安全的時候,有一個condifional線程安全,但某些操作序列可能需要外部同步。典型的例子是Hashtable。你可以在這裏找到關於這個主題的更多信息:http://www.ibm.com/developerworks/java/library/j-jtp09263.html – wax 2010-05-03 19:26:17

+0

@Goel:1)volatile變量,線程和堆棧限制,java.util.concurrent包等; 2)僵局; 3)唯一的「問題」本身就是阿姆達爾的法則。我也推薦Goetz的書「Java Concurrency in Practice」。 – 2010-05-06 19:20:46

2

不一定。例如,您可以在訪問對象變量的方法中同步(例如,在專用對象上放置一個鎖)。在其他情況下,您可以將作業委託給已經處理同步問題的一些內部對象。
有很多選擇,這一切都取決於你正在實施的算法。儘管「同步」關鍵字通常是最簡單的關鍵字。

編輯
上有沒有全面的教程,每一種情況是獨一無二的。學習它就像學習外語一樣:永不結束:)

但是肯定有幫助的資源。特別是Heinz Kabutz的網站上有一系列有趣的文章。
http://www.javaspecialists.eu/archive/Issue152.html (在網頁上看到的完整列表)

如果其他人有任何聯繫我很想也能看到。我發現整個話題相當混亂(並且可能是java核心中最困難的部分),尤其是因爲java 5中引入了新的併發機制。

玩得開心!

+0

你能提供一些關於這個教程的鏈接嗎? – 2010-05-03 18:53:20

+0

編輯答案以允許更好的格式化,例如linebrakes:/ – 2010-05-03 22:54:42

+0

+1。它真的幫助了我。 – 2010-05-04 09:35:51

1

以最一般的形式是。

不可變對象不需要同步。

此外,您還可以使用的可變實例變量各監視器/鎖(或團體存在的),這將有助於活力。以及只能同步數據更改的部分,而不是整個方法。

0

您可以使用同步方法,同步塊,併發工具(如Semaphore),或者如果您確實想要弄髒並弄髒,可以使用原子引用。其他選項包括聲明成員變量爲volatile,並使用類似AtomicInteger而不是Integer

這一切都取決於不同的情況,但也有廣泛的可併發工具 - 這些只是其中的一部分。

同步可能導致保持觀望僵局,其中每兩個線程有​​一個對象的鎖,並試圖獲取其他線程的對象的鎖。

同步也必須是全球性的一類,並且容易犯的錯誤是忘記同步的方法。當一個線程持有一個對象的鎖時,其他線程仍然可以訪問該對象的非同步方法。

1

同步方法名 VS同步的(對象)

這是正確的,並且是一個備選。我認爲只有同步所有方法才能更有效地同步對該對象的訪問。

雖然差可以是微妙的,如果使用了在單一線程相同的對象

即(使用方法synchronized關鍵字)

class SomeClass { 
    private int clickCount = 0; 

    public synchronized void click(){ 
     clickCount++; 
    } 
} 

當一個類被定義將是有用像這樣,一次只有一個線程可以調用click方法。

如果此方法是在單線程應用程序調用過於頻繁,會發生什麼?你會花費一些額外的時間來檢查該線程是否可以在不需要時獲取對象鎖。

class Main { 
    public static void main(String [] args) { 
     SomeClass someObject = new SomeClass(); 
     for(int i = 0 ; i < Integer.MAX_VALUE ; i++) { 
      someObject.click(); 
     } 
    } 
} 

在這種情況下,看到了檢查,如果該線程可以鎖定對象將被調用不必要Integer.MAX_VALUE(2 147 483 647)次。

因此,在這種情況下刪除同步關鍵字將運行得更快。

那麼,你會怎麼做,在多線程應用程序嗎?

你只同步對象:

synchronized (someObject) { 
    someObject.click(); 
} 

矢量VS的ArrayList

作爲附加的註釋,這種用法(syncrhonized方法名與synchonized(object))順便說一下,java.util.Vector現在被java.util.ArrayList取代的原因之一。許多Vector方法是同步的。

大多數情況下,單個線程應用程序或代碼片段中使用的列表(即jsp/servlets中的代碼是在單個線程中執行的),而Vector的額外同步對性能沒有幫助。

也是一樣Hashtable通過HashMap

+0

請注意,您還需要同步創建'someObject'變量,或讓它由主線程創建。 – Finbarr 2010-05-03 19:01:01

+0

@finbarr在多線程應用中,'someObject'必須由主線程創建。如果沒有,那麼我認爲它不會在從主線程開始的線程之間共享,並且如果它不可共享,則不需要同步它的創建。 – 2010-05-03 19:06:37

+0

@Oscar您已經指出了一個很好的觀點,但應用程序可以是單線程應用程序或多線程應用程序。所以,如果我編碼一個單線程應用程序,那麼爲什麼我需要同步任何東西。如果我編寫一個多線程應用程序,那麼爲什麼我不應該與整個實例方法或這些實例方法的一部分同步,而不是在每個地方同步'someObject.click()'。 – 2010-05-03 19:10:05

1

實際上getter a也應該同步,或者字段是volatile。這是因爲當你獲得一些價值時,你可能對價值的最新版本感興趣。您看到,​​塊語義不僅提供執行的原子性(例如,它確保一次只有一個線程執行此塊),而且還提供可見性。這意味着當線程輸入​​塊時,它會使其本地緩存失效,當它退出時,它會將所有已修改的變量轉儲回主內存。 volatile變量具有相同的可見性語義。

1

不可以。即使getter也必須同步,除非他們只訪問final字段。原因在於,例如,當訪問一個長整型值時,其他線程當前寫入的內容會發生一些微小的變化,並且只讀取前四個字節,而其他4個字節保持舊值。

+0

...但最終字段的獲取者不會,因爲他們的值永遠不會改變。 – paulcm 2010-05-04 19:09:53

+0

對不起,我讀了描述中的getters「和」final字段。你是對的。 – Daniel 2010-05-04 19:13:23