我要问的问题与 StringBuilder 和 StringBuffer 之间的差异有关,但不一样。我想看看如果 StringBuilder 同时被两个线程修改会发生什么。
我写了以下课程:
public class ThreadTester
{
public static void main(String[] args) throws InterruptedException
{
Runnable threadJob = new MyRunnable();
Thread myThread = new Thread(threadJob);
myThread.start();
for (int i = 0; i < 100; i++)
{
Thread.sleep(10);
StringContainer.addToSb("a");
}
System.out.println("1: " + StringContainer.getSb());
System.out.println("1 length: " + StringContainer.getSb().length());
}
}
public class MyRunnable implements Runnable
{
@Override
public void run()
{
for (int i = 0; i < 100; i++)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
StringContainer.addToSb("b");
}
System.out.println("2: " + StringContainer.getSb());
System.out.println("2 length: " + StringContainer.getSb().length());
}
}
public class StringContainer
{
private static final StringBuffer sb = new StringBuffer();
public static StringBuffer getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
最初我在 StringContainer 中保留了一个 StringBuffer。由于 StringBuffer 是线程安全的,一次只能有一个线程可以附加到它,因此输出是一致的 - 两个线程都将缓冲区的长度报告为 200,例如:
1: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
1 length: 200
2: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
2 length: 200
或者其中一个报告了 199 个,另一个报告了 200 个,例如:
2: abbabababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab
2 length: 199
1: abbababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababa
1 length: 200
关键是最后一个完成的线程报告长度为 200。
现在,我将 StringContainer 更改为具有 StringBuilder 而不是 StringBuffer 即
public class StringContainer
{
private static final StringBuilder sb = new StringBuilder();
public static StringBuilder getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
我希望某些写入会被覆盖,这正在发生。但是 StringBuilder 的内容和长度有时不匹配:
1: ababbabababaababbaabbabababababaab
1 length: 137
2: ababbabababaababbaabbabababababaab
2 length: 137
如您所见,打印的内容只有 34 个字符,但长度为 137。为什么会这样?
@Extreme Coders - 我刚刚又进行了一次测试:
2: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1 length: 150
2 length: 150
Java 版本:1.6.0_45,我使用的是 Eclipse 版本:面向 Web 开发人员的 Eclipse Java EE IDE。版本:Juno Service Release 2 内部版本 ID:20130225-0426
更新1:我在eclipse之外运行了这个,现在它们似乎匹配了,但有时我会收到ArrayIndexOutOfBoundsException:
$ java -version
java version "1.6.0_27"
OpenJDK Runtime Environment (IcedTea6 1.12.5) (6b27-1.12.5-0ubuntu0.12.04.1)
OpenJDK Server VM (build 20.0-b12, mixed mode)
$ java ThreadTester
1: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
1 length: 123
2: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
2 length: 123
$ java ThreadTester
2: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
2 length: 115
1: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
1 length: 115
$ java ThreadTester
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(String.java:862)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:408)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at StringContainer.addToSb(StringContainer.java:14)
at ThreadTester.main(ThreadTester.java:14)
2: abbbbbbababbbbabbbbababbbbaabbabbbaaabbbababbbbabaabaabaabaaabababaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2 length: 114
从 Eclipse 运行时也会发生 ArrayIndexOutOfBoundsException。
更新2: 发生了两个问题。StringBuilder 的内容与长度不匹配的第一个问题仅在 Eclipse 中发生,而不是在我在命令行中运行时发生(至少我在命令行上运行它的 100 多次从未发生过)。
ArrayIndexOutOfBoundsException 的第二个问题应该与 StringBuilder 类的内部实现有关,该类保留一个字符数组并Arrays.copyOf
在扩展大小时执行。但是无论执行顺序是什么,它仍然让我在扩展大小之前如何进行写入。
顺便说一句,我倾向于同意@GreyBeardedGeek 的回答,即整个练习都是浪费时间:-)。有时我们只能看到症状,即某些代码的输出,并想知道出了什么问题。这个问题先验地声明了两个线程正在修改(非常知名的)线程不安全对象。
更新 3:这是Java Concurrency in Practice p 的官方答案。35:
在没有同步的情况下,编译器、处理器和运行时可以对操作执行的顺序做一些非常奇怪的事情。试图推断在不充分同步的多线程程序中“必须”发生的内存操作的顺序几乎肯定是不正确的。
对不充分同步的并发程序进行推理非常困难。
在 p 的书中也有一个很好的例子NoVisibility
。34.