如何在android中的这两个线程之间建立关系

时间:2014-10-04 15:19:29

标签: android concurrency happens-before

以下是自定义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
    }
  }
}

我想做的是改变" i"的价值。在INIT-THREAD到100 并从MAIN线程尝试读回值。

我期待" i"在onResume和handleMessage为-100因为它们在MAIN线程中执行但是Log打印的值实际上是100。

在某种程度上,我试图重现每个人在普通java程序中所做的经典错误,但是android似乎会智能地避免它。

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

3 个答案:

答案 0 :(得分:0)

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

在一些测试运行中,您已经看到它产生了一些特定结果的事实并不能证明任何事情。虽然代码的行为是未定义的,但当您运行它时,它将执行某些内容。在硬件的任何特定位上,它甚至可能在大多数情况下都能做到这一点。

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

答案 1 :(得分:0)

这是在规范之前发生的事情:

  

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

     
      
  1. synchronized和volatile构造,以及Thread.start()和Thread.join()方法,可以在之前形成   关系。特别是:线程中的每个动作都发生在之前   该线程中的所有操作都会在程序的后续命令中出现。
  2.   
  3. 监视器的解锁(同步块或方法退出)发生在每个后续锁定之前(同步块或方法)   那个监视器的入口)。而且因为之前发生的关系   是传递的,解锁之前线程的所有动作   发生 - 在任何线程锁定之后的所有操作之前发生   监视。
  4.   
  5. 在对该相同字段的每次后续读取之前发生对易失性字段的写入。易失性字段的写入和读取具有相似之处   进入和退出监视器时的内存一致性效果,但确实如此   不需要互斥锁定。
  6.   
  7. 在启动线程中的任何操作之前发生对线程启动的调用。
  8.   
  9. 线程中的所有操作都发生在任何其他线程从该线程上的连接成功返回之前。
  10.         

    参考: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语句打印的值进行任何评论断言,准确。”

答案 2 :(得分:0)

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

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