Java 什么是;StringBuilder不是线程安全的”;什么意思?
我读过一些关于Java编程语言中Java 什么是;StringBuilder不是线程安全的”;什么意思?,java,string,multithreading,stringbuilder,stringbuffer,Java,String,Multithreading,Stringbuilder,Stringbuffer,我读过一些关于Java编程语言中String和StringBuilder的利弊的文章。在其中一篇文章中,作者提到: StringBuilder不是线程安全的,因此在多线程中使用 StringBuffer 不幸的是,我无法理解这意味着什么。请您解释一下String、StringBuilder和StringBuffer之间的区别,特别是在“线程安全”方面 如果您能用代码示例进行描述,我将不胜感激。如果多个线程正在修改一个StringBuilder的同一实例,则结果可能是意外的,即某些修改可能会丢失。
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);
}
}
如果许多线程调用addProperty方法,那么结果将是奇怪的(不可预测的结果)
最后,当您调用sb.toString()时,结果将是不可预测的。例如,它可能会带来像
1=2,ac=d=b,e=f
这样的输出,但您的期望值是1=2,a=b,c=d,e=f
因为StringBuilder不是同步的,而StringBuffer是同步的。在多线程环境中使用StringBuilder时,多个线程可以同时访问StringBuilder对象,并且无法预测它产生的输出,因此StringBuilder不是线程安全的
使用StringBuffer,我们可以克服线程安全的问题,其中StringBuffer是线程安全的,因为它是同步的,一次只有一个线程可以访问,因此可以预期和预测它产生的输出。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
实例,并且两个线程都试图同时追加一个字符。假设它们都得到值[count++]=c
语句,并且count
是1
。每一个将在值[1]
处将其字符写入缓冲区,然后更新计数。显然,那里只能存储一个字符。。。因此,另一个将丢失。此外,count
的一个增量可能会丢失
更糟糕的是,值[count++]=c即使两个线程没有同时到达,代码>语句也可能失败。原因是Java内存模型说,除非有适当的同步(一个“before”关系),否则不能保证第二个线程会看到第一个线程进行的内存更新。实际发生的情况取决于第一个线程的更新是否以及何时写入主内存
现在让我们看看StringBuffer.append(char)
:
这里我们看到append
方法是synchronized
。这意味着两件事:
- 两个线程不能同时在同一个
StringBuffer
对象上执行超类append
方法。因此,第一种情况不可能发生
synchronize
表示不同线程对StringBuffer.append的连续调用之间在之前发生。这意味着后一个线程一定会看到前一个线程中的更新
字符串
的大小写再次不同。如果我们检查代码,就会发现没有明显的同步。但这没关系,因为字符串
对象实际上是不可变的;i、 在String
API中没有任何方法会导致String
对象状态的外部可观察变化。此外:
final
实例变量和构造函数的特殊行为意味着所有线程都将看到任何字符串的正确初始状态
- 在
字符串
在幕后是可变的地方,hashCode()
方法将正确工作,无论线程是否看到hash
变量的最新更改
参考资料:
- StringBuilder的源代码-
- StringBuffer的源代码-
- 字符串的源代码-
方法内部的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);
}
}
参考:这可能很有用:主要用例是按顺序构建字符串,格式良好,使用StringBuilder/StringBuffer作为局部变量,而不需要多线程共享。因此,在大多数情况下,StringBuilder是主要的选择。即使您使用了StringBuffer
,您仍然可以得到您提到的不可预知的结果。Thread2可能会生成2个附加,然后Thread3生成1个附加,然后Thread2生成2个附加等等。这取决于JVM使用的线程管理策略。为了避免这种情况,您必须使整个方法addProperty
同步。StringBuffer
的区别在于它不允许在append
方法本身中有多个线程(append
是同步的),因此会覆盖彼此的更改-请参见下面Stephen的回答。@AdamBurley感谢您的回答。但我不明白你为什么解释
public synchronized StringBuffer append(char c) {
super.append(c); // calls the "AbstractStringBuilder.append" method above.
return this;
}
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);
}
}