2014-10-04 26 views
0

下面是一個自定義的App類和MainActivity類別代碼示例代碼示例:怎麼了之前發生關係建立在這兩個線程的Android

public class App extends Application { 
    private static String TAG = "APP"; 
    private int i; 

    @Override 
    public void onCreate() { 
    super.onCreate(); 
    Log.d(TAG, Thread.currentThread().getName()); 
    HandlerThread t = new HandlerThread("init-thread"); 
    t.start(); 

    i = -100; 

    Handler handler = new Handler(t.getLooper()); 

    handler.post(new Runnable() { 
     @Override 
     public void run() { 
      i = 100; 
     } 
    }); 

    handler.post(new Runnable() { 
     @Override 
     public void run() { 
      MainActivity.MainHandler h = new MainActivity.MainHandler(Looper.getMainLooper(), App.this); 
      h.sendEmptyMessage(0); 
     } 
    }); 
    } 

    public int getI() { 
    return i; 
    } 
} 

而且MainActivity類別:

public class MainActivity extends Activity { 
    private static String TAG = "ACT-1"; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 
    } 

    @Override 
    protected void onResume() { 
    super.onResume(); 
    App app = (App) getApplication(); 
    Log.e(TAG, "i: " + app.getI()); //prints 100 
    } 

    public static class MainHandler extends Handler { 
    private Application application; 
    public MainHandler(Looper looper, Application app) { 
     super(looper); 
     this.application = app; 
    } 

    @Override 
    public void handleMessage(Message msg) { 
     App app = (App) application; 
     Log.e(TAG, "MSG.what: " + msg.what); 
     Log.e(TAG, "i: " + app.getI()); //prints 100 
    } 
    } 
} 

我想要做的是將INIT-THREAD 中的「i」的值更改爲100,並嘗試從主線程讀取值。

我期待的價值「i」的的onResume和的handleMessage是-100,因爲它們是在主線程中執行,但日誌打印值實際上100

是在某種程度上,我試圖重現每個人在常規的java程序中都會犯的經典錯誤,但是android似乎明智地避免了它。

所以我有興趣瞭解android如何實現兩個線程之間的發生關係。

回答

0

在此程序中設置i的值時沒有發生以前的關係。該程序包含數據競爭並且出錯。

事實上,你已經看到它產生了一些特定的結果,在幾次測試運行中,根本沒有任何證據。雖然代碼的行爲是未定義的,但當您運行它時,它會做某事。在任何特定的硬件上,它甚至可能在大多數時候都會這樣做。

快速查看代碼,我沒有看到任何read-alter-rewrites,所以我認爲使volatile變爲i會使程序正確。然而,它不會對任何關於特定聲明打印的價值的評論斷言是準確的。

+0

實際上,由於Android的處理程序,實際上有一個發生之前的關係。閱讀下面的答案。 – Petrakeas 2015-09-29 14:32:42

0

這裏是之前發生規格:

Java語言規範的第17章定義了存儲器操作之前發生關係如讀取和共享變量的寫入。只有在寫入操作發生時,一個線程寫入的結果才能保證對另一個線程的讀取可見 - 在讀取操作之前。

  1. 同步的和揮發性的構建體,以及所述Thread.start()和的Thread.join()方法,就可以形成之前發生 關係。特別是:線程中的每個動作都會在該線程中的每個動作之前發生,該動作稍後按程序的順序進行。
  2. 監視器的解鎖(同步塊或方法退出)發生在相同監視器的每個後續鎖定(同步塊或方法 條目)之前。並且因爲在關係 之前發生的事情是可傳遞的,所以在解鎖 之前發生的線程的所有動作都會發生 - 在監視之後的任何線程鎖定之後的所有動作之前。
  3. 在每次後續讀取相同字段之前,都會發生對易失性字段的寫入。寫入和讀取易失性字段與輸入和退出顯示器具有相似的內存一致性效果,但不要求 不需要互斥鎖定。
  4. 在啓動的線程中執行任何操作之前,會發生在線程上啓動的調用。
  5. 線程中的所有操作都會在任何其他線程成功從該線程上的連接返回之前發生。

參考:http://developer.android.com/reference/java/util/concurrent/package-summary.html

我的代碼有評論解釋:

public class App extends Application { 
    private static String TAG = "APP"; 
    private int i; 

    @Override 
    public void onCreate() { 
    super.onCreate(); 
    Log.d(TAG, Thread.currentThread().getName()); 
    HandlerThread t = new HandlerThread("init-thread"); 
    t.start(); 

    i = -100; 

    Handler handler = new Handler(t.getLooper()); 

    handler.post(new Runnable() { 
     @Override 
     public void run() { 
      // before next line, i == -100 
      // because if you look into handler.post, 
      // it is using synchronized block to enqueue this Runnable. 
      // And when this Runnable is dispatched, 
      // it is using synchronized block of the same monitor. 
      // So from 2. you can conclude the i = -100; happens-before here. 
      i = 100; 
     } 
    }); 

    handler.post(new Runnable() { 
     @Override 
     public void run() { 
      MainActivity.MainHandler h = new MainActivity.MainHandler(Looper.getMainLooper(), App.this); 
      h.sendEmptyMessage(0); 
     } 
    }); 
    } 

    public int getI() { 
    return i; 
    } 
} 

直到現在還有之前發生關係的我。後來有沒有之前發生關係:

public class MainActivity extends Activity { 
    private static String TAG = "ACT-1"; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 
    } 

    @Override 
    protected void onResume() { 
    super.onResume(); 
    App app = (App) getApplication(); 
    Log.e(TAG, "i: " + app.getI()); //prints 100 
    // happens-before not guaranteed: 
    // there is no happens-before operation between background thread that 
    // sets i to 100 and main thread here is running. 
    } 

    public static class MainHandler extends Handler { 
    private Application application; 
    public MainHandler(Looper looper, Application app) { 
     super(looper); 
     this.application = app; 
    } 

    @Override 
    public void handleMessage(Message msg) { 
     App app = (App) application; 
     Log.e(TAG, "MSG.what: " + msg.what); 
     Log.e(TAG, "i: " + app.getI()); //prints 100 
     // happens-before not guaranteed for the same reason 
    } 
    } 
} 

,按照規範,同布萊克說,最簡單的方法固定的比賽是我改變揮發。 「然而,它不會對任何關於特定Log語句打印的價值的評論斷言是準確的。」

+0

幾乎完全贊同@賀林的評論。處理程序是一種安全發佈的方法,因此,如果onCreate中的「run」方法讀取「i」的值,它將保證讀取「-100」。然而,正如我所說的,在「run」方法中設置「i」與在「onResume」中讀取之間的關係之前,沒有發生任何事情。我需要花一點時間來完全確定讀取「handleMessage」 – 2015-09-30 14:38:40

+0

@Blake不應該'handler.post'確保'i = -100;'發生在它自己之前?我知道代碼順序沒有反映執行順序,但是沒有同步事件(例如,locking,handler.post)確保在該行發生之前的代碼 - 在該行之前? (我自己在這裏不是很清楚)。 – 2015-09-30 20:31:53

0

您的代碼工作原因是因爲Handler#post方法強制主線程和init-thread之間發生之前的關係。

如果你看看執行內部,MessageQueue#enqueueMessage有一個同步塊,使用self作爲監視器。當MessageQueue(在它自己的線程中)讀取並執行入隊的消息/ runnables時使用相同的監視器。

相關問題