我读过一些关于Java编程语言中String
和StringBuilder
的优缺点的文章。在其中一篇文章中,作者提到:
StringBuilder 不是线程安全的,在多线程使用时应该用 StringBuffer。
不幸的是,我不明白这是什么意思。您能否解释在“线程安全”的上下文中,String
、StringBuilder
和StringBuffer
之间的区别呢?
如果可以,请给出代码示例。
我读过一些关于Java编程语言中String
和StringBuilder
的优缺点的文章。在其中一篇文章中,作者提到:
StringBuilder 不是线程安全的,在多线程使用时应该用 StringBuffer。
不幸的是,我不明白这是什么意思。您能否解释在“线程安全”的上下文中,String
、StringBuilder
和StringBuffer
之间的区别呢?
如果可以,请给出代码示例。
StringBuilder
实例,则结果可能是意料之外的,即某些修改可能会丢失。因此,在这种情况下应该使用 StringBuffer。然而,如果每个线程只能由一个线程修改 StringBuilder
实例,最好使用 StringBuilder
,因为它更高效(线程安全会带来性能成本)。如果多个线程尝试更改 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);
}
}
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
是同步的),从而覆盖彼此的更改 - 请参见下面 Stephen 的回答。 - Adam BurleyStringBuilder
不支持线程安全的含义。但是你回答中提到的问题与StringBuilder
不支持线程安全无关。即使您使用了线程安全版本的StringBuilder
(即StringBuffer
,这就是我提到StringBuffer
的原因),您仍将遇到您回答中提到的问题。导致您提到的问题的原因是调用StringBuilder
的代码不是线程安全的,而不是StringBuilder
本身不是线程安全的。 - Adam BurleyStringBuilder
的线程安全问题在于其方法调用不同步。
考虑 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;
}
public synchronized StringBuffer append(char c) {
super.append(c); // calls the "AbstractStringBuilder.append" method above.
return this;
}
append
方法是synchronized
的。这意味着两件事:
两个线程不能同时在同一个StringBuffer
对象上执行超类的append
方法。因此第一种情况不可能发生。
synchronize
意味着不同线程对StringBuffer.append
的连续调用之间存在happens before。这意味着后面的线程保证能够看到先前线程所做的更新。
String
的情况又不同了。如果我们检查代码,我们会发现没有明显的同步机制。但这没关系,因为String
对象实际上是不可变的;即String
API中没有任何方法会导致String
对象状态的外部可观察更改。此外:
final
实例变量和构造函数的特殊行为意味着所有线程都将看到任何String
的正确初始状态。
在String
在幕后可变的唯一位置,无论线程是否看到hash
变量的最新更改,hashCode()
方法都将正常工作。
参考资料:
在方法内使用 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);
}
}