StringBuilder和StringBuffer类中的同步是什么意思?

5

我对“同步”的概念感到很困惑,从Java文档中我得到了以下信息:

一个可变的字符序列。该类提供了与StringBuffer兼容的API,但不保证同步。该类被设计为在单个线程使用字符串缓冲区的地方作为替代品。如果可能的话,建议在大多数实现中使用这个类 而不是StringBuffer,因为它会更快。

据我所知,同步与线程及其访问方式有关。

假设我有一个Web应用程序,在其中的一个方法中利用了StringBuilder,

  • 这里的“不保证同步”是什么意思?
  • 我应该担心什么?何时应该担心多个线程?有什么例子吗?
  • 我何时需要关注保证和非保证同步?
  • 有一个带有多个线程的Web应用程序的示例是什么?

一个例子将会非常感谢。

请注意,我知道多线程访问需要同步,因为它们需要访问相同的数据!我需要一个例子。

7个回答

5
如果有多个线程同时访问同一个StringBuilder实例,那么您应该关注同步。在这种情况下,您应该考虑使用StringBuffer代替。例如,在这种情况下,您应该考虑使用StringBuffer而不是StringBuilder:
public class Test implements Runnable
{
    public static StringBuilder sb = new StringBuilder();

    public void run ()
    {
        Test.sb.append ("something");
    }

    public static void main (String[] args)
    {
        // start two threads
        new Thread (new Test()).start();
        new Thread (new Test()).start();
    }
}

如果您有一个只限于某个方法的本地 StringBuilder 实例,那么不会有其他线程访问它的风险,因此您无需同步,并且使用 StringBuilder 会更加高效。

你能给我一个例子吗?在我的 Web 应用程序示例中,多个线程是什么意思? - Daniel Newtown
例如,如果您在某个类中有一个静态的StringBuilder实例,并且您的Web应用程序启动了多个线程,而且其中超过一个可以同时访问该StringBuilder实例,则应同步访问它。 - Eran
谢谢,那么有一个使用多个线程的Web应用程序的例子是什么? - Daniel Newtown
@DanielNewtown 我添加了一个简单应用程序的示例,其中包含多个线程。 - Eran
它将实现Runnable。 - Hiren
@Michal 不,我打算用我的代码片段作为一个例子,展示使用StringBuilder可能会导致问题。StringBuilder是非线程安全的类。 - Eran

1

synchronized关键字不会在生成的JavaDoc中显示,但如果您打开StringBuffer的源代码,您会发现可以改变实例状态的每个公共方法实际上在其签名中都有synchronized关键字。

例如getChars方法。

   /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
                                      int dstBegin)
    {
        super.getChars(srcBegin, srcEnd, dst, dstBegin);
    }

1

几乎从不使用 StringBuffer 是有道理的:

  • 大多数情况下,您可以使用本地的 StringBuilder 变量,并且只涉及一个线程,因此不需要同步
  • 在罕见的情况下,StringBuffer/Builder 变量是一个字段并且在多个线程之间共享,StringBuffer 的保证可能也不足够,因为每次调用 append 都将随机排序

想象一下:

public void m(String a, String b) {
  sharedStringBuffer.append(a).append(b);
}

StringBuffer 无法保证每次调用 m 都能使得 ab 相邻,因为可能会在中间插入其他的 append 调用...

所以,总结一下:

  • 如果变量不是共享的:使用 StringBuilder
  • 如果变量是共享的,而且你需要更多的同步控制:使用适当的同步控制的 StringBuilder

@DanielNewtown 针对您的使用情况选择适当的同步策略 - 没有通用规则... - assylias

1
假设您有一个变量myElement,它是一个StringBuffer。您的应用程序有来自不同报纸的新闻提要,并且您有多个线程从各种来源填充该提要。 线程将在myElement描述的DOM元素中找到其新信息。一旦他们找到它,他们就会修改myElement,以便其他线程知道在哪里定位新的新闻片段。由于同步,当它们访问相同的变量时,一个线程将阻止另一个线程,但在这种情况下不会发生,因此可能会发生这样的情况:一个线程在修改myElement并且只完成了一半时读取myElement,并获取对不存在的DOM部分的引用。

0

这样想一下。假设你有一个类,在它的方法之间共享一个 StringBuilder 实例。就像这样:

public class MyClass {
    private StringBuilder str = new StringBuilder();

    public void add(String str) {
         this.str.append(str);
    }

    public void remove(int start, int end) {
         this.str.delete(start, end);
    }

}

由于此实例在非同步方法之间共享,因此如果两个线程同时调用这些方法,则数据将不是您期望的数据。这就是不保证同步的含义。

您可以使用支持同步的StringBuffer而不是使用StringBuilder。

public class MyClass {
    private StringBuffer str = new StringBuffer();

    public void add(String str) {
         this.str.append(str);
    }

    public void remove(int start, int end) {
         this.str.delete(start, end);
    }

}

正如您所知,同步在多线程情况下是必要的,但也存在一些性能问题。

通常情况下,可以将 StringBuilder 用作局部变量。


0

除非您在线程之间共享StringBuffer,否则不需要它,此时同步是有意义的。StringBuilder是可变的,这意味着它的状态在线程之间可能会被不一致地查看。StringBuffer不会遇到这个问题(如果需要的话)。

StringBuilder就像StringBuffer v2一样。它是在Java库中添加的,此时开发人员对线程安全感到非常担心,并且在不需要线程安全时要求线程安全。

至于您发布的javadoc的加粗部分,同步会带来开销,因为StringBuilder是非同步的,所以它不会受到性能影响。

同步保证意味着由于它“与StringBuffer兼容”,您可以期望StringBuilder是同步的,但这可能不是情况(例如当JIT优化StringBuffer时)。

您不应该担心非保证或保证同步。您应该担心的是在共享时使用StringBuffer。只要StringBuilder仅在一个线程内共享或相应地受到保护,您就不应遇到任何问题。

您应该在这里担心使用StringBuffer:

final StringBuffer append = new StringBuffer();

void append() {
    append.append(". ");
}

new Thread(() -> append()).start();
new Thread(() -> append()).start();

你可以看到它被两个线程访问。注意为了初始化安全性而声明为final。


0
当您拥有在线程之间共享的StringBuffer时,您应该关注它。如果您不共享,则不会发生竞争条件,您可以自由使用StringBuilder。

请给我一个例子。 - Daniel Newtown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接