【问题标题】:how did the happens-before relation establish on these two threads in androidandroid中的这两个线程上的happens-before关系是如何建立的
【发布时间】:2014-10-04 15:19:28
【问题描述】:

以下是自定义 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 如何实现两个线程之间的先发生关系。

【问题讨论】:

    标签: android concurrency happens-before


    【解决方案1】:

    在这个程序中设置i 的值没有发生之前的关系。该程序包含数据争用且出现错误。

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

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

    【讨论】:

    • 其实是因为Android的Handler有happens-before关系。在下面阅读我的答案。
    【解决方案2】:

    这是发生之前的规范:

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

    1. synchronized 和 volatile 构造以及 Thread.start() 和 Thread.join() 方法可以形成happens-before 关系。特别是:线程中的每个动作都发生在之前 该线程中的每个操作都按程序顺序进行。
    2. 监视器的解锁(同步块或方法退出)发生在每个后续锁定(同步块或方法)之前 同一个监视器的条目)。因为happens-before关系 是传递的,线程在解锁之前的所有动作 发生在任何线程锁定之后的所有操作之前 监控。
    3. 对易失性字段的写入发生在对同一字段的每次后续读取之前。 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 语句打印的值做出任何准确的评论断言。”

    【讨论】:

    • 几乎完全同意@Helin 的cmets。处理程序是一种安全发布方法,因此,如果 onCreate 中的“运行”方法读取“i”的值,则可以保证读取“-100”。但是,正如我所说,“run”方法中的“i”设置与其在“onResume”中的读取之间没有发生之前的关系。我需要多花一点时间才能完全确定“handleMessage”中的内容
    • @Blake 不应该 handler.post 确保 i = -100; 先发生?我知道代码顺序不反映执行顺序,但同步事件(例如,锁定、handler.post)不能确保该行之前的代码发生在该行之前吗? (这里我自己也不是很清楚)。
    【解决方案3】:

    您的代码工作的原因是因为Handler#post 方法在主线程和init-thread 之间强制执行happens-before 关系。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-05
      • 1970-01-01
      • 1970-01-01
      • 2014-02-04
      • 1970-01-01
      • 1970-01-01
      • 2016-07-09
      • 2011-05-25
      相关资源
      最近更新 更多