0

以下は、カスタム App クラスと MainActivity クラス コードのサンプル コード例です。

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 に変更し、MAIN スレッドから値を読み取ろうとすることです。

onResume と handleMessage の "i" の値は MAIN スレッドで実行されているため、-100 になると予想していましたが、ログに出力される値は実際には 100 です。

ある意味で、私は誰もが通常の Java プログラムで行う古典的な間違いを再現しようとしていますが、Android はそれを賢く回避しているようです。

だから私は、アンドロイドが2つのスレッド間の事前発生関係をどのように達成しているかを理解することに興味があります.

4

3 に答える 3

0

iこのプログラムでの値の設定には、事前発生の関係はありません。プログラムにデータ競合が含まれており、エラーになっています。

数回のテスト実行で特定の結果が得られたという事実は、何の証拠にもなりません。コードの動作は定義されていませんが、実行すると何かが実行されます。ハードウェアの特定のビットでは、ほとんどの場合、それが行われることさえあります。

ざっとコードを見てみると、read-alter-rewrite が見当たらないので、ivolatile にすればプログラムは正しくなると思います。ただし、特定のLogステートメントが出力する値に関するコメント アサーションを正確にするわけではありません。

于 2014-10-04T15:34:01.783 に答える
0

コードが機能する理由は、Handler#postメソッドがメイン スレッドとinit-thread.

実装の内部を見ると、ある時点で、モニターとしてMessageQueue#enqueueMessage使用する同期ブロックがあります。selfMessageQueue (独自のスレッド内) がエンキューされたメッセージ/ランナブルを読み取り、実行するときに、同じモニターが使用されます。

于 2015-09-29T14:31:03.130 に答える
0

これが事前発生仕様です:

Java 言語仕様の第 17 章では、共有変数の読み取りや書き込みなどのメモリ操作に関する先行発生関係が定義されています。あるスレッドによる書き込みの結果は、書き込み操作が読み取り操作の前に発生した場合にのみ、別のスレッドによる読み取りに表示されることが保証されます。

  1. synchronized および volatile コンストラクト、および Thread.start() および Thread.join() メソッドは、事前発生関係を形成できます。特に、スレッド内の各アクションは、プログラムの順序で後で来るそのスレッド内のすべてのアクションの前に発生します。
  2. モニターのロック解除 (同期されたブロックまたはメソッドの終了) は、その同じモニターの後続のすべてのロック (同期されたブロックまたはメソッドのエントリ) の前に発生します。また、先行発生関係は推移的であるため、ロックを解除する前のスレッドのすべてのアクションは、そのモニターをロックしているスレッドに続くすべてのアクションの前に発生します。
  3. 揮発性フィールドへの書き込みは、同じフィールドの後続のすべての読み取りの前に発生します。揮発性フィールドの書き込みと読み取りには、モニターの開始と終了と同様のメモリ整合性効果がありますが、相互排他ロックは必要ありません。
  4. スレッドでの start の呼び出しは、開始されたスレッドのアクションの前に発生します。
  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;
  }
}

これまで、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
    }
  }
}

仕様によると、ブレイクが言ったように、レースを修正する最も簡単な解決策は、 i を揮発性に変更することです。「ただし、特定の Log ステートメントが出力する値に関するコメントの主張を正確にすることはできません。」

于 2015-04-01T00:46:26.970 に答える