【问题标题】:Empty synchronized block revisited: data visibility重新访问空同步块:数据可见性
【发布时间】:2012-11-23 13:05:48
【问题描述】:

我阅读了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() 之后)。

我的问题:

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

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

    过去的情况是规范暗示了一定的记忆 发生了屏障操作。但是,规格现在已经改变,并且 原始规范从未正确实施。它可以用来等待 让另一个线程释放锁,但协调 其他线程已经获得锁会很棘手。

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

【问题讨论】:

  • 看看docs.oracle.com/javase/7/docs/api/java/util/concurrent/…。也许信号量或屏障更适合您的用例。
  • 感谢您的反馈。我的软件很复杂,这里没有列出一些部分,所以请假设我需要对我所询问的功能有所了解。我开发的组件必须比 Semaphore 或 CycleBarrier 提供的更通用。
  • 如果你的意思也是我所说的“每个其他线程调用 sendMessageWithHandler 恰好一次”,那么让我们忽略“恰好一次”。这里只是为了强调该方法不经常使用,因此线程获取自己的 mHandler 副本是没有意义的。
  • 给你一些简单的阅读:developer.android.com/training/articles/smp.html
  • 谢谢,非常有用的阅读!

标签: java android multithreading synchronized


【解决方案1】:

正如我所见,mHandler 在创建之前没有被其他线程访问,并且在生命周期内也没有改变。因此,可以安全地从其他线程读取它而无需任何同步。可以肯定的是,您可以在同步块中阅读它:

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

由于“其他每个线程都将调用sendMessageWithHandler(...) 一次”,因此开销绝对可以忽略不计。坦率地说,在多个方法调用和线程创建的背景下,您对尽量减少同步工具的使用(“我也不想让它变得易变”)的痴迷看起来不够充分。只有在每秒发生数百万次同步时才值得打扰。

【讨论】:

  • 谢谢,这是一个关于性能的好点。我只是不喜欢代码中的冗余元素(例如 volatile + synchronized),它们降低了我的可读性。
  • ... 但在代码更改的情况下提高可靠性。他们还考虑如何通过最小化线程交互和最大化使用本地线程内存来安全地消除它们,从而提高性能。
  • 这确实是一个“永恒的”问题:为未来计划什么,什么(“为改变而设计”)。对于初学者来说,你建议放置多余的 volatile 可能没问题,但在这个具体的项目中,我知道未来可能发生的变化的性质,在这个具体的案例中,用不必要的 volatile 污染代码并不是一个好的解决方案。
【解决方案2】:

您可能不需要任何同步。

(thread 1)          (thread 2)

write
  |
start thread 2
                 \
                    ...
                     |
                    read  

read 保证可以看到write


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

【讨论】:

  • 谢谢。就计算机内部发生的情况而言,这是否意味着 thread 2 不能拥有 mHandler 的过时副本,因为它以前从未访问过它,对吧?换句话说,如果我的软件保证(以某种方式,例如通过更高级别的构造)第一次读取来自另一个线程的非最终非易失性变量 之后最后写到那个变量,我安全吗?
  • “高级结构” = 例如在这种情况下,这意味着线程 2 在 last write 到 mHandler 之后启动。这可以概括吗?例如。如果线程 2 可能在 last write 到 mHandler 之前启动,但应用程序中的一些约束保证它只会在 last write 之后读取它,该怎么办?在这种情况下,我想我也很安全。 (当然,我知道,健壮且可维护的代码也很重要。)
  • 这取决于after的定义。 Java 内存模型定义了可被视为正式的before/after 关系的synchronize-with 和happens-before 关系。我图中的箭头符合这种关系,所以读取是after 写入。见docs.oracle.com/javase/specs/jls/se7/html/…
猜你喜欢
  • 2022-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-30
  • 2018-02-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多