【问题标题】:What does " StringBuilders are not thread-safe" mean?“StringBuilders 不是线程安全的”是什么意思?
【发布时间】:2015-01-07 17:21:30
【问题描述】:

我读过一些关于StringStringBuilder 在Java 编程语言中的优缺点的文章。在其中一篇文章中,作者提到:

StringBuilder 不是线程安全的,所以在多线程中使用 字符串缓冲区

很遗憾,我无法理解这意味着什么。您能否解释一下StringStringBuilderStringBuffer 之间的区别,尤其是在“线程安全”的上下文中。

如果您能用代码示例进行描述,我将不胜感激。

【问题讨论】:

标签: java string multithreading stringbuilder stringbuffer


【解决方案1】:

如果多个线程正在修改 StringBuilder 的同一个实例,结果可能会出乎意料 - 即某些修改可能会丢失。这就是在这种情况下应该使用 StringBuffer 的原因。但是,如果每个线程 StringBuilder 实例只能由一个线程修改,则最好使用 StringBuilder,因为它会更高效(线程安全伴随着性能成本)。

【讨论】:

  • 主要用例是按顺序构建一个格式良好的字符串,使用 StringBuilder/StringBuffer 作为局部变量,无需多线程共享。所以大多数情况下StringBuilder是首选。
【解决方案2】:

如果多个线程试图改变 StringBuilder 对象的值,那么结果会很奇怪。看下面的例子,

private StringBuilder sb = new StringBuilder("1=2");

public void addProperty(String name, String value) {
    if (value != null && value.length() > 0) {
        if (sb.length() > 0) {
            sb.append(',');
        }
        sb.append(name).append('=').append(value);
    }
}

如果有很多线程调用 addProperty 方法,那么结果会很奇怪(不可预知的结果)。

Thread1: addProperty("a", "b");
Thread2: addProperty("c", "d");
Thread3: addProperty("e", "f");

最后,当您调用 sb.toString() 时,结果将是不可预测的。例如,它可能会带来像1=2,ac=d=b,e=f 这样的输出,但你的期望会是1=2,a=b,c=d,e=f

【讨论】:

  • 即使你使用了StringBuffer,你仍然可以得到你提到的不可预知的结果。可能会发生 Thread2 进行 2 次追加,然后 Thread3 进行 1 次追加,然后 Thread2 进行 2 次追加等。这取决于 JVM 使用的线程管理策略。为了避免这种情况,您必须使整个方法 addProperty 同步。与StringBuffer 的区别在于它不允许多个线程在append 方法本身中(append 是同步的),因此会覆盖彼此的更改 - 请参阅下面斯蒂芬的回答。
  • @AdamBurley 感谢您的回复。但我不明白你为什么向我的回答解释 StringBuffer 。问题是关于为什么 StringBuilder 不是线程安全的,所以我解释了为什么它不是线程安全的,但我没有解释哪个是安全的或如何确保安全,因为这实际上不是问题。在我的回答中,我没有解释任何关于 StringBuffer 的内容。所以对我来说,您的评论似乎与我的回答无关。
  • 问题是StringBuilder 不是线程安全的意味着什么。但是您的回答中提到的问题与StringBuilder 不是线程安全的无关。即使您使用了StringBuilder 的线程安全版本(即StringBuffer,这就是我提到StringBuffer 的原因),您仍然会遇到答案中提到的问题。导致您提到的问题的问题是您编写的调用StringBuilder 的代码不是线程安全的,并且不是 StringBuilder 本身不是线程安全的。
【解决方案3】:

StringBuilder 的线程安全问题是 StringBuilder 上的方法调用不同步。

考虑StringBuilder.append(char)方法的实现:

public StringBuilder append(boolean b) {
    super.append(b);
    return this;
}

// from the superclass
public AbstractStringBuilder append(char c) {
     int newCount = count + 1;
     if (newCount > value.length)
         expandCapacity(newCount);
     value[count++] = c;
     return this;
 }

现在假设您有两个线程共享一个StringBuilder 实例,并且都尝试同时追加一个字符。假设他们同时到达value[count++] = c; 语句,并且count1。每个将其字符写入缓冲区value[1],然后更新count。显然那里只能存储一个字符……所以另一个字符会丢失。此外,count 的增量之一可能会丢失。

更糟糕的是,value[count++] = c; 语句可能会失败,即使两个线程没有同时到达那里。原因是 Java 内存模型说除非有适当的同步(或者更准确地说,happen before 关系),否则不能保证第二个线程会看到第一个线程所做的内存更新线。实际发生的情况取决于第一个线程的更新是否以及何时写入主内存。


现在让我们看看StringBuffer.append(char)

public synchronized StringBuffer append(char c) {
    super.append(c);  // calls the "AbstractStringBuilder.append" method above.
    return this;
}

这里我们看到append 方法是synchronized。这意味着两件事:

  • 两个线程不能同时在同一个StringBuffer 对象上执行超类append 方法。因此第一种情况不可能发生。

  • synchronize 表示在不同线程对StringBuffer.append 的连续调用之间存在发生之前。这意味着保证后面的线程可以看到前面线程中所做的更新。


String 的情况再次不同。如果我们检查代码,我们会发现没有明显的同步。但这没关系,因为String 对象实际上是不可变的;即String API 中没有任何方法会导致String 对象的状态发生外部可观察变化。另外:

  • final 实例变量和构造函数的特殊行为意味着所有线程将看到任何String 的正确初始状态。

  • String 在幕后可变的一个地方,hashCode() 方法将正常工作,无论线程是否看到对 hash 变量的最新更改。


参考资料:

【讨论】:

    【解决方案4】:

    因为 StringBuilder 不是同步的,而 StringBuffer 是同步的。在多线程环境中使用 StringBuilder 时,多个线程可以同时访问 StringBuilder 对象,并且无法预测它产生的输出,因此 StringBuilder 不是线程安全的。 ..

    使用 StringBuffer 我们可以克服线程安全问题,其中 StringBuffer 是线程安全的,因为它是同步的,一次只能访问一个线程,因此可以预期和预测它产生的输出。

    【讨论】:

      【解决方案5】:

      方法中的StringBuilder 是安全的。

      public void addProperty(String name, String value) {
          private StringBuilder sb = new StringBuilder("1=2");
      
              if (value != null && value.length() > 0) {
                  if (sb.length() > 0) {
                      sb.append(',');
              }
              sb.append(name).append('=').append(value);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2015-06-02
        • 1970-01-01
        • 2014-05-19
        • 1970-01-01
        • 1970-01-01
        • 2018-04-29
        • 1970-01-01
        • 2018-12-03
        • 2019-05-06
        相关资源
        最近更新 更多