2016-07-07 42 views
9

我知道您可以通過編寫對finalvolatile字段的引用來安全地發佈非線程安全對象,該字段稍後將由另一個線程讀取,前提是發佈時線程創建該對象的對象會放棄對其的引用,以便它不再幹擾或不安全地觀察該對象在另一個線程中的使用。本地最終引用的安全發佈

但是在這個例子中,沒有明確的final字段,只有final局部變量。 如果來電者丟棄了對unsafe的引用,這是否安全發佈?

void publish(final Unsafe unsafe) { 
    mExecutor.execute(new Runnable() { 
     public void run() { 
      // do something with unsafe 
     } 
    } 
} 

我發現了幾個Q &作爲,像this one,這表明final局部變量是隱式「複製」到匿名類。這是否意味着上面的例子等同於這個?

void publish(final Unsafe unsafe) { 
    mExecutor.execute(new Runnable() { 
     final Unsafe mUnsafe = unsafe; 
     public void run() { 
      // do something with mUnsafe 
     } 
    } 
} 

編輯澄清:

Unsafe可以是任何東西,但說這是這樣的:

public class Unsafe { 
    public int x; 
} 

而且mExecutor是什麼,滿足Executor合同。

+1

您的執行程序正在使用線程安全隊列。要看到這個問題,你必須在線程之間傳遞對象,而不用在任何地方使用合適的內存屏障 –

+1

@PeterLawrey class Executor {void execute(Runnable r){}}' - 這裏沒有隊列。但問題是*可能*有效還是... – Marco13

+0

BTW'Unsafe'是具有一個單類,雖然你可以創建更多的人... –

回答

4

雖然,不可否認,我不能完全肯定,我得到了你的問題的實際點,(在評論中指出的)的問題很可能不是一個真正的問題,你的具體情況,也許相關

import java.util.concurrent.ExecutorService; 

class Unsafe 
{ 

} 

class SafePublication 
{ 
    private final ExecutorService mExecutor = null; 

    public void publish(final Unsafe unsafe) 
    { 
     mExecutor.execute(new Runnable() 
     { 
      @Override 
      public void run() 
      { 
       // do something with unsafe 
       System.out.println(unsafe); 
      } 
     }); 
    } 
} 

人們可以編譯並獲得兩個.class文件::

    01的見解可以從測試/示例

    考慮下面的類來獲得

  • SafePublication.class
  • SafePublication$1.class的內部類

反編譯的內部類的類文件產生如下:

class SafePublication$1 implements java.lang.Runnable { 
    final Unsafe val$unsafe; 

    final SafePublication this$0; 

    SafePublication$1(SafePublication, Unsafe); 
    Code: 
     0: aload_0 
     1: aload_1 
     2: putfield  #1     // Field this$0:LSafePublication; 
     5: aload_0 
     6: aload_2 
     7: putfield  #2     // Field val$unsafe:LUnsafe; 
     10: aload_0 
     11: invokespecial #3     // Method java/lang/Object."<init>":()V 
     14: return 

    public void run(); 
    Code: 
     0: getstatic  #4     // Field java/lang/System.out:Ljava/io/PrintStream; 
     3: aload_0 
     4: getfield  #2     // Field val$unsafe:LUnsafe; 
     7: invokevirtual #5     // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
     10: return 
} 

我們可以看到,對於final參數,的確有場在這堂課中介紹。這個字段是val$unsafe,它是一個final field in the class file sense,它在構造函數中被初始化。 (這與您發佈的第二個代碼片段不完全相同,因爲第二個代碼片段包含兩個最終字段,它們都使用相同的值進行初始化,但關於安全發佈的問題,應該是一樣的)。

+0

這正是我所掌握的。 –

1

的問題似乎被這個答案可以部分回答:

Java multi-threading & Safe Publication

至少關於「安全發佈」。

現在,如果來電者拋棄它的參考變量將是安全的,因爲存在除了在最後的局部變量沒有參考變量。

以及有關代碼示例 - 在我的眼睛裏帶着代碼片段是等價的。引入一個額外的局部變量不會改變語義,在這兩種情況下,編譯器都會將引用識別爲不可變的,並讓您使用它。

編輯 - 我要離開這個部分來記錄我的任擇議定書的問題的誤解

爲了澄清 - 我採取的finalvolatile使用如本例授予,所以能見度正確的內存屏障餐飲的對象引用在那裏,唯一的一點是非線程安全對象的可能的可變性,它不能使用內存障礙來保證,並且實際上與它們無關。它可以通過適當的同步或只留下一個內容引用來處理。

EDIT2 - 讀OP的評論

我剛纔看了一下JSR 133 FAQ後 - AFAIU參考的安全出版物使用內存屏障不保證所提到的unsychronized領域引用的對象對象也可見。 final也不volatile

如果我沒有曲解這個FAQ只同步一臺監視器上定義了「之前發生」爲關係的所有寫操作釋放同步鎖被另一個線程獲取同一顯示器上的鎖之前一個線程做。

我可能會誤會,但它聽起來好像也引用的對象的非同步字段將可見。

如果使用final關鍵字(如在你的例子,其中的參數被插入爲final場) - 僅引用對象的實例字段本身是final保證對象的施工結束後可見。

但在BlockingQueue(並作爲其實施LinkedBlockingQueue)我看到任何不​​關鍵字 - 它似乎使用了一些非常聰明的代碼使用volatile領域實現同步,對我來說,聽起來不像同步在JSR 133

描述意義上的監視器上這將意味着通過執行程序使用常見的阻塞隊列不保證您的Unsafe情況下的非最終字段的可見性。雖然參考本身可以使用關鍵字final進行安全發佈,但此參考指向的域的安全發佈也要求字段爲final,或者與作者和讀者共享的監視器同步。

不要拍信使:-)。

3

您在第一個示例中留下了一些重要的代碼:mExecutor對象可能擁有BlockingQueuemExecutor.execute(r)調用可能調用q.put(r)將您的任務添加到隊列中,然後稍後,工作程序線程在調用r.run()之前調用r=q.take()以獲取任務。

阻塞隊列的put()take()方法將在會通過「安全發佈」成語之一建立兩個線程事件之間的關係「之前發生」建立的同類。

無論在調用q.put(r)之前第一個線程在內存中更新的內容是否保證在調用返回之前在第二個線程中都可見。

+0

您提出的觀點我認爲在這裏忽略了一點:OP明確地談論了非線程安全的對象,「發生之前」的語義有助於設置引用,但不會針對調用者*突變可變對象。* Runnable有已被放入隊列並且可能與Runnable中正在使用的同一時間完全一致。所以實際上它在訪問所討論的局部變量方面確保沒有任何線程安全和安全發佈。 –

+0

@TomaszStanczak,OP似乎說這不會發生,「......只要發佈時,創建該對象的線程就會放棄對該對象的引用,以便它不再幹擾或不安全地觀察該對象在另一個線程中的使用「。 –

+0

而這正是保證,而不是'put'和'get'之間的「發生之前」關係,而這本身並不能保證。其實已經有函數調用有這個關係,會發生什麼是:1.調用publish,2.放,3. get。如果原始變量引用被丟棄,那麼'publish'的調用就足夠了,而不管接下來會發生什麼。 –