2009-11-27 44 views
28

我具有其中一個多線程的Java代碼:同步在本地變量

  • 幾個線程讀取來自同步共享存儲(即,作爲其結果,某些線程可能引用相同的對象)狀態的對象;
  • 然後每個線程調用一個方法process()並在那裏傳遞它的對象;
  • process()以某種方式處理對象,這可能導致更改對象狀態;
  • 這些狀態更改應該同步。

我創建了一個方法,像:

public void process(Foo[] foos) { 
    for (final Foo foo : foos) { 
     if (foo.needsProcessing()) { 
      synchronized (foo) { 
       foo.process(); // foo's state may be changed here 
      } 
     } 
    } 
} 

據我所知,這看起來合法的。然而,IntelliJ的檢查抱怨同步本地變量,因爲「不同的線程很可能有不同的本地實例」(這是而不是對我有效,因爲我是而不是初始化方法中的foos)。

本質上我想在這裏實現的是具有方法Foo.process()同步(這不是我的選擇,因爲Foo是第三方庫的一部分)相同。

我習慣了沒有黃色標記的代碼,所以來自社區的任何建議都是值得讚賞的。這對於在本地進行同步真的很糟糕嗎?有沒有其他的方法可以適用於我的情況?

在此先感謝!

回答

21
if (foo.needsProcessing()) { 
    synchronized (foo) { 
     foo.process(); // foo's state may be changed here 
    } 
} 

我認爲在上述片段中的競爭條件可能導致foo.process()偶爾被同一個對象上調用兩次。它應該是:

synchronized (foo) { 
    if (foo.needsProcessing()) { 
     foo.process(); // foo's state may be changed here 
    } 
} 

這真的是壞對當地人同步?

同步本地人本身並不差。真正的問題是:

  • 不同的線程是否同步在正確的對象,以實現適當的同步,並

  • 別的東西是否可以通過對這些對象同步造成問題。

+1

對,謝謝! 順便說一句,你對這裏的主要問題有什麼看法?這與當地人同步真的很糟糕嗎? – 2009-11-27 14:43:21

1

將環形體重構爲單獨的方法,並以foo作爲參數。

+0

我需要在這個方法的參數也 – 2009-11-27 14:08:25

+0

是同步的,但它不會是一個局部變量,然後。 – 2009-11-27 14:47:07

+2

的IntelliJ有另一種類型的檢查「不同的線程很可能有不同的方法的參數」 :) – 2009-11-27 14:54:21

1

沒有自動智能是完美的。我認爲你在局部變量上同步的想法是非常正確的。因此,也許按你的方式(這是正確的),並建議JetBrains他們應該調整他們的檢查。

0

沒有什麼不對您的同步集合中的一個對象。你可以嘗試用正常更換的foreach的for循環:

for (int n = 0; n < Foos.length; n++) { 
    Foo foo = Foos[n]; 

    if (null != foo && foo.needsProcessing()) { 
     synchronized (foo) { 
      foo.process(); // foo's state may be changed here 
     } 
    } 
} 

甚至(這樣,探測器將不會在Foo foo跳閘):

for (int n = 0; n < foos.length; n++) { 
    if (null != foos[n] && foos[n].needsProcessing()) { 
     synchronized (foos[n]) { 
      foos[n].process(); // foos[n]'s state may be changed here 
     } 
    } 
} 

不使用臨時值,以防止多foos[n]並不是最佳做法,但如果它可以防止不必要的警告,您可能會忍受它。添加評論爲什麼此代碼具有異常形式。 :-)

2

IDE是應該幫助你,如果犯了錯誤,你不應該彎腰討好它。

您可以在IntelliJ中禁用此檢查。 (有趣的是在「線程問題」默認啓用的唯一的一個。這是最常見的錯誤的人做什麼?)

0

在.NET世界中的物體,有時隨身攜帶的鎖對象屬性。

synchronized (foo.getSyncRoot()) { 
    if (foo.needsProcessing()) { 
     foo.process(); // foo's state may be changed here 
    } 
} 

這允許對象,得到了不同的鎖定對象,這取決於它的實現(例如委託監視器到底層數據庫連接或東西)。

4

斯蒂芬C'S答案有問題,他進入了很多同步鎖毫無意義,奇怪的是更好的方式來格式化是:

public void process(Foo[] foos) { 
     for (final Foo foo : foos) { 
      if (foo.needsProcessing()) { 
       synchronized (foo) { 
        if (foo.needsProcessing()) { 
         foo.process(); // foo's state may be changed here 
        } 
       } 
      } 
     } 
    } 

獲取同步鎖有時需要一段時間,如果舉行有些事情正在改變。這可能會改變當時foo的需求處理狀態。

你不想等待鎖,如果你不需要處理的對象。當你獲得鎖定後,它可能不需要處理。所以,儘管它看起來很傻,新手程序員可能傾向於刪除其中一個檢查,但實際上只要foo.needsProcessing()是一個可忽略的函數,它就是這樣做的好方法。


將這一回的主要問題,當它是你想基於一個數組,那是因爲時間是至關重要的局部值同步的情況。在這些情況下,你想要做的最後一件事就是鎖定數組中的每一項或者處理兩次數據。如果你有幾個線程在做一堆工作,並且很少需要觸摸相同的數據,但是可能會很好,你只會同步本地對象。

這樣做將只命中鎖定當且僅當,處理該FOO需要處理會導致併發的錯誤。當你想基於數組中精確的對象進行同步時,你基本上會想在這種情況下使用雙重門控語法。這可以防止雙處理foos並鎖定任何不需要處理的foo。

您只剩下極少數情況下阻塞線程,甚至只輸入一個鎖,並且它的確切時間很重要,並且您的線程僅在阻塞導致併發錯誤時才被阻止。