【问题标题】:java threads synchronizationjava线程同步
【发布时间】:2011-01-16 19:05:03
【问题描述】:

在下面的类中,方法getIt()线程安全吗?为什么?

public class X { 
  private long myVar; 
  public void setIt(long  var){ 
    myVar = var; 
   }  
   public long getIt() { 
     return myVar; 
  } 
}

【问题讨论】:

  • 这是作业吗?还是面试题?
  • @finnw:只要你喜欢 :) 有什么区别......?如果你没有贡献...?要我改标签吗?
  • 我只是好奇,因为我在面试时被问到了类似的问题。无需更改标签。

标签: java multithreading synchronization


【解决方案1】:

不,不是。至少,在缺乏原子 64 位内存访问的平台上不会。

假设线程 A 调用 setIt,将 32 位复制到支持值所在的内存中,然后在复制其他 32 位之前被抢占。

然后线程 B 调用getIt

【讨论】:

  • 即使在提供原子 64 位内存访问的平台上,您也必须同步 get 和 set 方法。或者你必须使成员不稳定。有关详细信息,请参阅我的答案。
  • 你的答案是正确的,但是太具体了。关于为什么不同步的原因有很多 - 请参阅@tangens 答案。
【解决方案2】:

它应该是,通常是,但不保证是线程安全的。 CPU 缓存中具有不同版本的不同内核可能存在问题,或者存储/检索对于所有架构都不是原子的。使用AtomicLong 类。

【讨论】:

    【解决方案3】:

    因为它是只读方法。您应该同步set 方法。

    EDIT :我明白为什么 get 方法也需要同步。很好地解释了菲尔罗斯。

    【讨论】:

    • 这两个方法必须同步才能保证线程安全。否则,get 方法可能会产生部分更改的版本。
    【解决方案4】:

    不,不是,因为 long 在 java 中不是原子的,所以一个线程可以在 setIt 方法中写入 long 的 32 位,然后 getIt 可以读取值,然后 setIt 可以设置其他 32 位.

    所以最终结果是 getIt 返回一个从未有效的值。

    【讨论】:

      【解决方案5】:

      它不是线程安全的。 Java 中longdouble 类型的变量被视为两个独立的32 位变量。当另一个线程读取两半时,一个线程可能正在写入并写入了一半的值。在这种情况下,读者会看到一个本不应该存在的值。

      要使这个线程安全,您可以将myVar 声明为volatile(Java 1.5 或更高版本)或同时声明setItgetIt synchronized

      请注意,即使 myVar 是 32 位 int,您仍然可能会遇到线程问题,即一个线程可能正在读取另一个线程已更改的过期值。这可能是因为该值已被 CPU 缓存。要解决此问题,您需要再次将 myVar 声明为 volatile(Java 1.5 或更高版本)或同时声明 setItgetIt synchronized

      还值得注意的是,如果您在随后的setIt 调用中使用getIt 的结果,例如x.setIt(x.getIt() * 2),那么您可能想在两个调用中使用synchronize

      synchronized(x)
      {
        x.setIt(x.getIt() * 2);
      }
      

      如果没有额外的同步,另一个线程可能会更改 getItsetIt 调用之间的值,从而导致另一个线程的值丢失。

      【讨论】:

      • +1 这是大多数开发人员不知道的事实。除了 volatile,您还可以使用 Atomic* 类之一(在 java.util.concurrent 中找到)来更好地表达您的意图。
      【解决方案6】:

      这不是线程安全的。即使您的平台保证long 的原子写入,synchronized 的缺乏也使得一个线程调用setIt() 成为可能,即使在此调用完成后,另一个线程也可能调用getIt(),并且此调用可能返回 myVar 的旧值。

      synchronized 关键字不仅仅是一个线程对块或方法的独占访问。它还保证第二个线程被告知变量的变化。

      因此,您要么必须将这两种方法都标记为synchronized,要么将成员myVar 标记为volatile

      关于同步有一个很好的解释here

      原子操作不能交错,因此可以使用它们而不必担心线程干扰。但是,这并不能消除同步原子操作的所有需要​​,因为仍然可能出现内存一致性错误。使用 volatile 变量可以降低内存一致性错误的风险,因为对 volatile 变量的任何写入都会与后续读取该相同变量建立起之前发生的关系。这意味着对 volatile 变量的更改始终对其他线程可见。更重要的是,这也意味着当一个线程读取一个 volatile 变量时,它不仅会看到 volatile 的最新更改,还会看到导致更改的代码的副作用。

      【讨论】:

        【解决方案7】:

        getter 不是线程安全的,因为它不受任何保证最新可见性的机制的保护。您的选择是:

        • 使 myVar 成为最终状态(但你不能改变它)
        • 使myVar易变
        • 使用同步访问myVar

        【讨论】:

          【解决方案8】:

          AFAIK,现代 JVM 不再拆分 long 和 double 操作。我不知道有任何参考表明这仍然是一个问题。例如,请参阅在 Sun 的 JVM 中不使用同步的 AtomicLong。

          假设您想确定这不是问题,那么您可以使用同步 get() 和 set()。但是,如果您正在执行添加之类的操作,即 set(get()+1),那么这种同步不会给您带来太多好处,您仍然需要为整个操作同步对象。 (解决这个问题的更好方法是对同步的 add(n) 使用单个操作)

          但是,更好的解决方案是使用 AtomicLong。这支持 get、set 和 add 等原子操作,并且不使用同步。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多