【问题标题】:Is entering synchronized block atomic?进入同步块是原子的吗?
【发布时间】:2015-05-26 20:34:24
【问题描述】:

你知道java中的同步块是否保证是原子的吗?

想象下面的案例

线程_1,2:

synchronized(object){object.modify();}

(对象是共享变量。)

想象 thread_M 会改变对对象的引用

synchronized(object){object = new Object()}

现在假设线程 1 和 2 正在竞争获取对象的锁定

有没有可能发生以下情况:
1. Thread1:读取旧对象
2. ThreadM:修改对象引用并释放旧对象锁
3. Thread2:读取新对象;检查锁;锁定它
4. Thread1: check lock (ok cos old object was read);锁定它
现在两个线程都有一个锁并修改相同的(新)对象

所以要具体说明我的问题 - 在某个地方保证同步(对象)中的步骤(1 和 4)是原子的(如步骤 3 中所述)?

【问题讨论】:

  • 同步块不是原子的!但它们通过提供适当的锁定机制来帮助防止竞争条件。
  • @jsn 你是什么意思他们不是原子的?在同一对象的同步块上运行的两个线程将彼此原子地完成。
  • @JohnVint 另一个线程可能在同步块的语句之间调度,因此它不是一个不可打印的全部成功或失败操作。
  • @jsn:我知道其他线程可能会运行。当然我的问题只与同步的“对象”有关。如果对象是最终的,那么显然没有 2 个同步块能够进入同步(对象)。关于线程 1,2,它应该是“原子的”(我会说不是交错)。但问题是,如果 Thread_M 可以更改对象引用本身(尽管在同步块中),锁定过程是否安全。
  • @JohnVint:“同一对象的同步块”是什么意思?块(即代码)不属于对象,块属于类。 OP 想知道两个线程如何同时进入同一个同步块。答案是,他们可以在不同对象上同步时做到这一点。在上面的示例中,这是无意中发生的,因为程序员认为synchronized(object) { ... }变量 进行操作,而实际上它是在对变量恰好在块被引用的瞬间引用的对象进行操作输入。

标签: java multithreading synchronization synchronized


【解决方案1】:

假设你有一些变量,foo:

Foo foo;

假设它持有一个对象的引用:

foo = new Foo(...);

假设我们有一个synchronized 块:

synchronized(foo) {
    ...
}

synchronized 关键字对变量foo 进行操作,并且对同步块中的语句进行操作。

synchronized 关键字在这里唯一能做的就是防止其他线程同时在同一个 instance 上同步。

如果你重新分配变量 foo 来引用一些不同的实例,而线程 A 在块内,那么其他线程 B 将能够同时进入同一个块,因为两个线程中的每一个都会在不同的实例上同步。

【讨论】:

  • 我知道!这就是我想要的 :) 基本原理是在同步块中,我只更改要同步的对象。因此,如果对象更改,我不在乎其他线程仍在修改旧对象......但我的问题再次是,在将对象更改为新对象期间是否会发生 2 个新线程将进入同步并修改相同的新对象。这取决于在 synchronized() 块开始时如何实现锁定
  • @VitBernatik 谨防编写“棘手”的代码。大多数程序员根据熟悉的模式进行思考。如果我在synchronized(foo){...} 块内看到foo = ...;,我的眼睛会看到一个错误——我什至不必考虑它。如果该分配实际上对于该方法按照预期的方式运行是必要的,那对我来说将是一个惊喜,因为它是一种不熟悉的模式。我敢打赌,很多有经验的开发人员都不熟悉它。谷歌以“最小惊讶原则”解释为什么您不希望其他开发人员不熟悉您的设计模式。
【解决方案2】:

可以object 上同步时重新分配object,但我想不出重新分配用于锁定的字段是个好主意的场景。

在线程 M 退出其同步块之前,没有其他线程能够获得对旧值 object 的锁定,但是一旦新对象可见,另一个线程将能够获得对新对象的锁定那个线程。

线程在释放锁之前所做的修改保证对之后获取锁的线程可见。但是由于您正在重新分配锁本身,因此获取线程可能看不到它已被更改,并在旧值上获取锁。然后他们仍然看不到 object 已被重新分配。

object 声明为volatile 变量将确保其“当前”值用于锁定。但它不会阻止两个线程同时修改同一个实例:

  1. 线程 M 获取旧值锁定。线程 1 读取旧值。
  2. 线程 M 更改值。
  3. 线程 M 释放对旧值的锁定。线程 2 读取新值。
  4. 线程 1 获取旧值锁定。线程 2 获取新值的锁定。
  5. 线程 1 读取新值。线程 2 读取新值。
  6. 线程 1 修改新值。线程 2 修改了新值。

为避免所有这些,只需创建一个单独的对象用于锁定,永远不要更改它。

【讨论】:

  • 不,我真的在问我是否可以更改对我正在同步的对象的引用(假设更改本身在同步块中)。如果更改本身不在同步块中,我可以自己提出不一致的示例 - 请参阅以下线程中的示例响应 stackoverflow.com/questions/6910807/…
  • 您的第一句话应该说,“您可以在对象上同步时重新分配对象,但这几乎总是一个可怕的错误。”以“你可以......”开始你的回答会让它看起来像......是个好主意。
  • 很好的例子可能是您正在同步您想要一致更改的对象。因此,当对象更改时,您的意图是能够同时更改它 - 没有逻辑理由等待旧对象完成配置。是否有一些论据为什么这会是一个可怕的想法? (除了否定地回答我的问题并给我一些参考,说同步块不安全)
  • @VitBernatik 如果您担心在配置旧对象时阻塞某些线程,有更好的方法来处理它,例如读写锁。
  • @erickson:是的,我现在将其更改为使用 Lock。我唯一关心的是对象本身的一些基本稳定性。我有一个函数可以从文件中加载新的配置对象。所以我不在乎一些挂起的任务是用旧配置完成的。我只关心所有线程都使用一致的旧配置或一致的新配置。
【解决方案3】:

您正在同步该“对象”指向的对象,而不是保存该值的变量。

但是,由于两段代码在向前移动之前在您的对象上同步,所以您是安全的——尽管这是一种糟糕的设计模式。

如果您使用同步方法而不是同步代码块,您可能会发现更少的混乱。

另外,只是我的看法,但 synchronized(object) 似乎是一种非常糟糕的设计模式。这只是我的看法,但我从不这样做。

【讨论】:

  • 是的,但我不会更改对象所持有的值 - 请参阅我的示例中的 thread_M 更改对象本身。这可能会造成问题。
  • 他不安全。正如他所怀疑的那样,线程 1 和 2 都可以修改新对象,因为它们被锁定在两个不同的实例上。
  • 我不确定您真正想要完成什么。你同步一个对象——而不是一个对象的句柄。如果你有两个用于同一个对象的同步块,那么它们是同步的,但是你正在做的事情真的很可怕。说真的,同步方法会更安全,也更容易理解。
  • synchronized(object) 1) 允许您在除this 之外的其他对象上进行同步,2) 允许您使用较小的块进行同步。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 2018-03-03
  • 2015-03-02
  • 2015-06-22
相关资源
最近更新 更多