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”的值为 -100,因为它们在 MAIN 线程中执行,但 Log 打印的值实际上是 100。

在某种程度上,我试图重现每个人在常规 java 程序中都会犯的经典错误,但 android 似乎聪明地避免了它。

所以我有兴趣了解android如何实现两个线程之间的happens-before关系。

4

3 回答 3

0

i在此程序中设置 的值时没有发生之前的关系。该程序包含数据竞争并且出错。

您已经看到它在几次测试运行中产生了一些特定的结果,这一事实根本无法证明任何事情。虽然代码的行为是未定义的,但当你运行它时,它会做一些事情。在任何特定的硬件上,它甚至大部分时间都可以做到这一点。

快速查看代码,我没有看到任何 read-alter-rewrites,所以我认为制作ivolatile 会使程序正确。但是,它不会使任何关于特定Log语句打印的值的评论断言准确。

于 2014-10-04T15:34:01.783 回答
0

这是发生之前的规范:

Java 语言规范的第 17 章定义了内存操作(例如共享变量的读取和写入)的发生前关系。只有当写操作发生在读操作之前,一个线程写的结果才能保证对另一个线程的读可见。

  1. synchronized 和 volatile 构造,以及 Thread.start() 和 Thread.join() 方法,可以形成happens-before 关系。特别是:线程中的每个动作都发生在该线程中的每个动作之前,这些动作按程序的顺序出现在后面。
  2. 监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法入口)之前。并且由于happens-before关系是可传递的,因此线程在解锁之前的所有动作都发生在任何线程锁定该监视器之后的所有动作之前。
  3. 对 volatile 字段的写入发生在对同一字段的每次后续读取之前。volatile 字段的写入和读取具有与进入和退出监视器类似的内存一致性效果,但不需要互斥锁定。
  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;
  }
}

到现在为止,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
    }
  }
}

根据规范,正如 Blake 所说,最简单的解决方案修复竞赛是将 i 更改为 volatile。“但是,它不会对特定 Log 语句打印的值做出任何评论断言,准确无误。”

于 2015-04-01T00:46:26.970 回答
0

您的代码有效的原因是该Handler#post方法强制执行主线程和init-thread.

如果你查看实现的内部,在某些时候它MessageQueue#enqueueMessage有一个同步块self用作监视器。当 MessageQueue(在它自己的线程中)读取和执行排队的消息/可运行文件时,使用相同的监视器。

于 2015-09-29T14:31:03.130 回答