【问题标题】:Is this variable being safely accessed by using synchronization?是否通过使用同步安全地访问此变量?
【发布时间】:2012-01-04 13:17:01
【问题描述】:

我有一个案例,其中 Java 类有一个包含同步块的超类。

Class SuperClassA {
     private Bitmap bmpA;

     protected abstract Bitmap createBitmap();

     public void run() {
          synchronized (this) {
               bmpA = createBitmap();
          }
     }

     // some other codes.
}

Class SubClassB extends SuperClassA {
     private Bitmap outBmpB;

     protected Bitmap createBitmap() {
          outBmpB = ..... // create and process "outBmpB".

          Bitmap bmp;
          bmp = ..... // create and process "bmp".
          return bmp;
     }

     public Bitmap getOutBmpB() {
          Bitmap tempBmp;
          synchronized (this) {
               tempBmp = outBmpB.clone();
          }
          return tempBmp;
     }

     // some other codes.
}

Class B 中的“getOutBmpB()”方法由一个线程运行,而 ClassB 中继承的“run()”方法由另一个线程运行。 ClassB 中实现的“createBitmap()”方法应该在“run()”方法内的同步块中运行。

我的问题是我不确定ClassB中新定义的类变量“outBmpB”是否被两个线程安全访问。我不确定“run()”方法中的“同步(this)”块是否也会“锁定”仅在 ClassB 中定义的“outBmpB”变量?如果没有,那么我可以在“createBitmap()”实现中添加一个“同步(this)”块。例如

 protected Bitmap createBitmap() {
      synchronized (this) {
           outBmpB = ..... // create and process "outBmpB".
      }

      Bitmap bmp;
      bmp = ..... // create and process "bmp".
      return bmp;
 }

感谢您的任何建议。

劳伦斯

【问题讨论】:

  • createBitmapA()createBitmap() 是不是同一个方法,有错别字?
  • 那个代码很难理解。由于分配给 outBmpB,同步完成。嵌套同步没问题。您覆盖 createBitmap 的事实,并且在一个版本中不同步可能存在样式问题,但看起来它可能会起作用。
  • @JoopEggen 那么他是否会覆盖createBitmap()? :)
  • @alf:(首先抽象声明有一个错字:createBitmapA。)答案是:可以写在子类@Overriden中,所以它被覆盖了。

标签: java nested synchronized


【解决方案1】:

您不必必须同步它,因为它已经在超类中同步并且没有其他调用,但您应该这样做。您对createBitmap() 的实现依赖于超类的实现细节。 在您访问共享字段的每一点进行同步。

由于这个事实,您当前的子类的代码很容易出错!

虽然通过this 进行同步是有问题的,但通过其他人无法访问的私有对象进行同步是一个更好的主意。因此,您的代码不能被使用您的类对象的客户端破坏并在其上进行同步。

【讨论】:

  • 我不会对this 部分如此苛刻;在公共 API 中,这确实可能是一个问题——但几乎只在 API 中。 +1 提到该方法容易出错:)
  • 很抱歉,它听起来很刺耳,但不应该。这应该是一个提示,提醒您进行防御并意识到它可能必须通过this 同步的副作用。 (即微软警告在 C# 中同步 this 并建议永远不要这样做。我认为他们在这一点上是正确的。)
  • @Erick Robertson 也许这说明得更清楚:“你必须同步并且它是在超类方法中完成的,这是唯一一个调用createBitmap()的方法。但是当调用层次结构或超类实现发生变化时它可能会破坏同步。所以子类方法应该自己进行适当的同步。"
  • @ErickRobertson 现在好点了吗?我也会将 正确地 改为一些东西,错误,不那么令人鼓舞,但这样的改变太大了。
  • @user1129812 很高兴您能理解这一点。我敢打赌 MS 和 Fabian 关心的是不同的部分:this 通常随处可见。也就是说,每个可以持有该对象的人都可以锁定this,写作synchronized (thisVeryObject)。这意味着外部代码会影响您执行的锁定。这对 API 来说极其重要:如果你锁定了某个东西,你最好把它隐藏起来。它在您自己的代码中不太重要,因为任何人都会在您的对象实例上同步的可能性很小(毕竟谁在乎?)。
【解决方案2】:

这不是关于“锁定”一个变量:你只是不能这样做。您的代码中确实有适当的锁定。但是,“线程安全”意味着“任何合理使用都是安全的”。由于您更改 outBmpB 的方法是 protected(即包成员和所有子级都可以使用),因此您最好显式同步。

作为一般规则,每次访问共享可变状态时,都必须确保自己持有锁。在你的情况下,你不确定。同时,Java 锁是可重入的,因此嵌套同步不会受到伤害——或许还能让您免于意外。

【讨论】:

  • 同一个对象上的嵌套同步永远不会受到伤害,原因很明显。
  • @Viruzzo 实际上可以,如果您再添加一个对象:当您通过代码传播嵌套锁时,锁排序会更难。哪怕是同一个对象。 特别是如果它是同一个对象。
【解决方案3】:

一般来说,最好锁定您正在使用的对象(bmpAoutBmpB 在您的情况下)而不是容器对象,除非您确实需要这样做(在这种情况下同步方法会更好)。

根据您的具体情况,synchronized (this) 块将相互排斥,所以是的,如果您不在其他任何地方调用 createBitmap(),它会起作用,所以最好是在createBitmap() 内部同步,而不是在run() 内部同步。

【讨论】:

  • 由于我们更改了引用,锁定它并没有多大意义。
  • @alf 这是一个普遍的考虑;在这种特殊情况下,outBmpB 在分配后永远不会被修改,所以他甚至不需要同步开始。
  • 这是一种误导性的一般考虑,通常是错误的和有害的。鉴于 OP 显然是新手,教授不良做法似乎不是一个好主意。
  • @alf 与盲目同步相比,这本身是一种最糟糕的做法吗?锁定特定对象,假设您要修改它,似乎完全合理。根据引用的变化,你只需要小心分配给类成员作为同步块的最后一个操作。
  • 好吧,让我们从头开始:你只需要小心分配给类成员,因为synchronized块的最后一个操作显然是错误的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-17
  • 2016-05-30
相关资源
最近更新 更多