【问题标题】:Sharing data without synchronization - is this code thread safe?在不同步的情况下共享数据——这个代码线程安全吗?
【发布时间】:2017-10-24 09:11:09
【问题描述】:

我在 Peter Lawry 的博客 here 上找到了此代码。他提到这个类不需要任何进一步的同步。

我正在努力提高我对并发性以及如何避免不必要的同步的了解,因此我试图从 java 内存模型的角度来了解如何推理这种情况。

对字符串数组的引用是最终的,字符串本身是不可变的,但对包含在数组中的字符串的引用是可变的

  • 在另一个线程更新值后,一个线程仍然可以看到 null 至少在理论上不是可能的吗?
  • 或者是不是我们不在乎字符串是否被多次实习?
  • 或者 JVM 是否提供了一些我缺少的额外保证?

    public class StringInterner {
       private final String[] interner;
       private final int mask;
    
       public StringInterner(int capacity) {
          int n = Maths.nextPower2(capacity, 128);
          interner = new String[n];
          mask = n - 1;
       }
    
       private static boolean isEqual(@Nullable CharSequence s, @NotNull CharSequence cs) {
          if (s == null)
             return false;
          if (s.length() != cs.length())
             return false;
          for (int i = 0; i < cs.length(); i++)
             if (s.charAt(i) != cs.charAt(i))
                return false;
          return true;
       }
    
       @NotNull
       public String intern(@NotNull CharSequence cs) {
          long hash = 0;
          for (int i = 0; i < cs.length(); i++)
             hash = 57 * hash + cs.charAt(i);
          int h = (int) Maths.hash(hash) & mask;
          String s = interner[h];
          if (isEqual(s, cs))
             return s;
          String s2 = cs.toString();
          return interner[h] = s2;
       }
    }
    

【问题讨论】:

    标签: java concurrency thread-safety


    【解决方案1】:

    在您的情况下,我认为我们不在乎 String 是否被实习两次。内存模型注意在分配数组值时不会发生任何坏事。这是一个相关的问题:java array thread-safety

    从并发的角度来看,它在没有同步的情况下工作,因为数据是安全的。因此该类在并发访问中正常工作。

    如果您想严格要求实习生只在您需要同步时才发生,但这是有代价的。这取决于您的用例对您来说正确性是什么。 (正如 gudok 所指出的那样:由于散列,独立于并发仍然发生不止一次)

    【讨论】:

    • 注意:即使显式同步也不能保证实习生不会发生多次。这是因为多个字符串可能散列到同一个桶中。
    • @gudok:是的,这是真的。我只是从并发的角度来看。我会将其添加到答案中
    • @Sorontur - 感谢您的回答,所以为了确认,我是否正确地推断一个线程可以在另一个线程之后看到一个过时的字符串值(null 或以前存储的值)是否在该内存位置留存了一个字符串? – Shane 50 分钟前
    • @Shane:内存模型不保证严格的可见性。这意味着即使在写入新引用之后,另一个线程仍然可以看到旧引用。这可能是由于 CPU 中的寄存器、管道或指令优化而发生的。我在实践中从未观察到这一点,但理论上它可以发生。
    • @Sorontur 可见性问题是真实存在的并且可能发生。如果你想阅读或实践它,我推荐书的第一章,七周内的七个并发模型。有一些很好的例子,有详细的解释
    【解决方案2】:

    我同意 Sorontur 关于可见性的评论。此代码可能会产生意想不到的结果(重现场景可能很棘手)。看来intern 方法不是线程安全的。多个线程可以在多个内核上并行运行,通常每个内核都有自己的缓存。如果一个线程更新intern中的任何变量,它会立即更新它的缓存,但其他内核的缓存不会同时更新,需要一些时间,同时其他线程可能会使用旧值。因此,为了应对这种情况,您可以使用 volatile 变量,但这会影响性能。因此,shared memory model 上的多线程是性能和效率之间的权衡。

    注意:我认为在并发线程上可以看到意外行为,它并不特定于并行执行

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-02
      • 2020-02-04
      • 2011-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多