2011-05-26 79 views
3

我的測試表明,即使該類是無狀態的,並且方法之間共享的所有狀態都作爲方法的參數傳遞給方法,但下面的代碼不是線程安全的。多個線程正在調用以下類的單個實例。最終參數的使用是否會阻止類成爲線程安全的?

public class ThingFinder { 

    Token findFoo(TokenIterator<? extends Token> iterator, 
        Token start, final Token limit) { 

     Token partOfFoo = searchForward(iterator, start, new TokenSearcher() { 
      int maxTokens = 5; 

      @Override 
      public SearchAction assessToken(Token aToken) { 
       if (limit != null && (aToken.getStart() >= limit.getStart())) { 
        return SearchAction.STOP; 
       } 
       if (maxTokens-- == 0) { 
        return SearchAction.STOP; 
       } 
       if (isAThing(aToken)) { 
        return SearchAction.MATCH; 
       } else { 
        return SearchAction.IGNORE; 
       } 
      } 
     }); 
     return partOfFoo; 
    } 

    public Token extractAThing(TokenIterator<? extends Token> iterator) { 
     Token start = findStart(iterator); 
     Token limit = findLimit(iterator, start); 

     return findFoo(iterator, start, limit); 
    } 
} 

這樣做的目的是爲這個類是線程安全的,由於是無狀態的事實,這需要傳遞從方法到方法參數的方法之間共享所有的狀態。然而,測試表明,有時候我們在這條線得到一個空指針異常:

 if (limit != null && (aToken.getStart() >= limit.getStart())) { 

看來,有時空值檢查和getStart的調用的參數限制變得無效。

注意,方法findFoo聲明限制參數是最終:

Token findFoo(TokenIterator<? extends Token> iterator, Token start, 
        final Token limit) { 

難道最終方法參數不堆棧幀的情況下,而是一個實例的方法的所有調用之間共享?如果確實在所有調用中共享一個實例,那麼這是否意味着使用最終參數會使類本質上處於不安全狀態?

+1

您確定限制爲空嗎?如果aToken爲null,則會得到相同的異常。 – MikeTheReader 2011-05-26 22:42:10

+1

你有多確定「極限」是罪魁禍首,而不是「aToken」? – 2011-05-26 22:43:18

+0

aToken保證不爲空。從框架中可以清楚地看到回叫但很難在帖子中顯示。另外,我在測試期間爲aToken添加了空指針檢查。 – ditkin 2011-05-26 22:45:02

回答

3

最終方法參數是不是在堆棧幀上,而是在方法的所有調用中共享一個實例?如果確實在所有調用中共享一個實例,那麼這是否意味着使用最終參數會使類本質上處於不安全狀態?

不,我想你是在這裏混合的東西。

final修飾符的作用是允許將局部變量limit複製到匿名實例的合成變量中。這個複製發生在這個實例的構造過程中,然後它將被assessToken方法使用。這個綜合變量仍然是最終的(或者至少沒有被修改過),因此如果你的框架沒有做反射魔術(這個並行的,這仍然),這裏應該沒有問題。

仍然每次呼叫findFoo都會有自己的參數。

正如裏德所說,getStart結果的拆箱是一個更可能的罪魁禍首。

+0

getStart返回一個int。我已經閱讀了關於jvm中的「方法區域」及其與最終變量的關係。匿名內部類可以通過方法調用嗎?如果是的話,它會訪問什麼限制變量? – ditkin 2011-05-27 00:14:04

+0

我明白你的解釋。如果限制是最終的,那麼它可以被複制,這就是爲什麼它必須是最終被匿名內部類使用。很好的解釋,我會以此爲答案,因爲它讓我不必擔心匿名內部類如何訪問最終參數限制 – ditkin 2011-05-27 02:00:04

+0

是的,這些方法本地類(包括匿名類)是一個非常有限的閉包版本,他們只對最終變量起作用,因爲這些變量不會改變,從而繞過了這個問題。 – 2011-05-27 09:04:54

1

如果getStart()返回Long或Integer類型,但值爲null,也可能會產生相同的異常。異常是由於> =比較而取消整數或長整型引起的,但本機類型不能爲空。

+0

getStart()返回一個int。 – ditkin 2011-05-26 23:23:37

+0

鑑於您在此添加了足夠的響應以消除常見的嫌疑犯,我將繼續將兩個getLimit調用的結果放入局部變量中(當然,儘管框架已得到保證,但通過null檢查進行保護),因此NPE將在沒有其他操作的情況下發生,並確定罪魁禍首。如果仍然抓不到頭,將getStart標記爲同步並查看會發生什麼。 – Reed 2011-05-26 23:59:36

+0

除了我的專業知識...也許傑裏米曼森[鏈接](http://jeremymanson.blogspot.com)可以說一些光。他研究了Java內存模型JSR 133。 – Reed 2011-05-27 00:52:17

0

關於您的問題一般來說,每個方法調用都會獲得自己的最終參數實例。 Final不會創建一個靜態變量。它只是允許變量只設置一次。

1

似乎在null值檢查和getStart的調用之間的某個時間參數限制變爲空。

這是不可能的。在您正在測試和使用limit時,它是(有效)一個線程受限對象的私有最終實例變量。這是最終的事實意味着它不會改變。如果它是非空的,它將保持這種狀態。對象被線程限制的事實意味着除了當前的線程之外沒有其他線程可以到達它。

(唯一潛在的線程安全的問題,我能想到威力的出現,如果你的searchForward方法通過TokenSearcher對象到不同的線程;即對象是線程限制可能會認爲是這樣嗎?)

我認爲,真正的問題是另一回事:

  • aStart參數可以是null
  • 如果的getStart()方法中的一種或其他被聲明爲返回一個盒裝型,他們可能會返回null

無論是哪種可能導致在該線路上的NPE。


匿名內部類可以住過去的方法調用?

它可以做。它完全取決於searchForward方法調用的功能。

如果有啥限制變量將訪問它?

匿名內部類的代碼訪問保存爲TokenSearcher對象的實例變量的limit的副本。當對象被創建時它被初始化,並且是final

+0

getStart返回一個int。我已經閱讀了關於jvm中的「方法區域」及其與最終變量的關係。匿名內部類可以通過方法調用嗎?如果是的話,它會訪問什麼限制變量? - ditkin 5分鐘前 添加評論 – ditkin 2011-05-27 00:26:09