重新访问空的同步块:数据可见性

时间:2012-12-05 16:25:05

标签: java android multithreading synchronized

我读了this stackoverflow主题,结论似乎是一个空的synchronized块总是可以用一个更好的解决方案来避免。这个话题对我来说还有一些不清楚的部分,我将整合到我的下面的帖子中。

假设我们有这样一个类:

public class MyThreadClass extends Thread {
  private final OtherComponent mOtherComponent;
  private final Object mLock = new Object();
  private MyHandler mHandler;

  public MyCustomThread(OtherComponent otherComponent) {
      mOtherComponent = otherComponent;      

  public void run() {

      mHandler = new Handler() {...}

      // Other init operations

      mOtherComponent.onMyCustomThreadInitialized();

      // ... other operations (Looper.loop(), actually)
  }

  public void sendMessageWithHandler(int what, int arg) {
       synchronized (mLock) {}
       Message msg = mHandler.obtainMessage(what);
       msg.arg1 = arg;
       mHandler.sendMessage(msg);
  }

  public void useHandlerInAnotherWay(...) {
       synchronized (mLock) {
            // useful code that needs actual mutual exclusion
       }
       mHandler.sendMessage(...);
  }
}

我的申请的相关部分以下列方式运作:

  1. 创建并启动MyThreadClass线程。
  2. 作为mOtherComponent.onMyCustomThreadInitialized()的间接结果,我的应用程序的另一部分将开始产生其他线程。 (注意它们不是从这个调用同步启动的,这就是为什么我说这是间接后果。唯一的一点是,mHandler已经被这些其他线程已开始
  3. 其他主题中的每一个都会恰好调用sendMessageWithHandler(...)
  4. 然而,其他线程(即上面提到的线程)再次调用useHandlerInAnotherWay(...),这可能在任何时候发生(当然在mOtherComponent.onMyCustomThreadInitialized()之后)。
  5. 我的问题:

    1. 如果我是正确的,那么从mHandler以外的其他线程访问myThreadClass时,必须保证最新的数据可见性,因为它不是final领域。我也不想让它volatile,因为除了这几个sendMessageWithHandler(..)调用之外,没有同步的其他线程没有使用mHandler(我不想{{1}在不必要的地方不必要地存在开销。换句话说,当通过volatile从那些其他线程访问mHandler时,那里的useHandlerInAnotherWay()带有“有用代码”(即实际上需要成为互斥主题的代码) )还保证调用者线程正确地看到synchronized。但是,在mHandler中,代码不需要互斥,因此我决定将空的同步块放到sendMessageWithHandler(..)的开头。它是否正确?我的问题有更好的解决方案吗?

    2. 我链接到的另一个stackoverflow线程有以下答案(它不是被接受的,但被多次投票):

        

      以前是规范暗示某些记忆的情况   障碍行动发生了。但是,规范现在已经改变了   原始规格从未正确实施。它可能用于等待   另一个线程释放锁,但协调该   其他线程已经获得锁定将是棘手的。

      这是否意味着空sendMessageWithHandler(...)不再提供内存屏障功能?如果我在线查看关于synchronized的Java文档,他们会提到所有内存都会因为它而更新(即在监视器输入时从“主内存”更新线程副本,并且从主机内容更新“主内存”)监控退出)。但是他们没有提到关于空synchronized块的任何内容,所以我不清楚这一点。

2 个答案:

答案 0 :(得分:2)

您可能不需要任何同步。

(thread 1)          (thread 2)

write
  |
start thread 2
                 \
                    ...
                     |
                    read  

read保证会看到write


synchronized的语义是严格执行的,即使它是一个空块。

答案 1 :(得分:1)

正如我所看到的,mHandler在创建之前不会被其他线程访问,并且在生命周期内不会被更改。因此,可以安全地从其他线程读取它而无需任何同步。为了绝对确定,您可以在synchronized块中读取它:

public void sendMessageWithHandler(int what, int arg) {
   MyHandler mHandler;
   synchronized (mLock) {
      mHandler=this.mHandler;
   }
   // the rest of code unchanged
}

由于“其他每个线程都会调用sendMessageWithHandler(...)一次”,因此开销几乎可以忽略不计。坦率地说,你对最小化同步设施的使用(“我不想让它变得易失”)的痴迷在多个方法调用和线程创建的背景下看起来不合适。只有当同步每秒发生数百万次时才值得打扰。