问题描述
StringBuffer
是同步的,但 StringBuilder
不是!这已在 StringBuilder 和 StringBuffer 之间的区别 中进行了深入讨论.
StringBuffer
is synchronized but StringBuilder
is not ! This has been discussed deeply at Difference between StringBuilder and StringBuffer.
那里有一个示例代码(由@NicolasZozol 回答),它解决了两个问题:
There is an example code there (Answered by @NicolasZozol), which address two issues:
- 比较这些
StringBuffer
和StringBuilder
的性能 - 显示
StringBuilder
在多线程环境中可能会失败.
- compares the performance of these
StringBuffer
andStringBuilder
- shows the
StringBuilder
could fail in a multithread environment.
我的问题是关于第二部分,究竟是什么让它出错了?!多次运行代码时,堆栈跟踪显示如下:
My question is about second part, exactly what makes it to go wrong?!When you run the code some times, the stack trace is displayed as below:
Exception in thread "pool-2-thread-2" java.lang.ArrayIndexOutOfBoundsException
at java.lang.String.getChars(String.java:826)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:416)
at java.lang.StringBuilder.append(StringBuilder.java:132)
at java.lang.StringBuilder.append(StringBuilder.java:179)
at java.lang.StringBuilder.append(StringBuilder.java:72)
at test.SampleTest.AppendableRunnable.run(SampleTest.java:59)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
当我追踪代码时,我发现实际抛出异常的类是:String.class
在 getChars
调用 System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
根据System.arraycopy
javadoc:
When I trace down the code I find that the class which actually throws the exception is: String.class
at getChars
method which calls System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
According to System.arraycopy
javadoc:
从指定的源数组复制一个数组,从指定位置,到目的地的指定位置大批.从源复制数组组件的子序列src 引用的数组到dest 引用的目标数组.复制的组件数等于长度参数.....
IndexOutOfBoundsException - 如果复制会导致数据访问超出数组边界.
IndexOutOfBoundsException - if copying would cause access of data outside array bounds.
为简单起见,我将代码完全粘贴在这里:
For simplicity I have exactly paste the code here:
public class StringsPerf {
public static void main(String[] args) {
ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
//With Buffer
StringBuffer buffer = new StringBuffer();
for (int i = 0 ; i < 10; i++){
executorService.execute(new AppendableRunnable(buffer));
}
shutdownAndAwaitTermination(executorService);
System.out.println(" Thread Buffer : "+ AppendableRunnable.time);
//With Builder
AppendableRunnable.time = 0;
executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
StringBuilder builder = new StringBuilder();
for (int i = 0 ; i < 10; i++){
executorService.execute(new AppendableRunnable(builder));
}
shutdownAndAwaitTermination(executorService);
System.out.println(" Thread Builder: "+ AppendableRunnable.time);
}
static void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // code reduced from Official Javadoc for Executors
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (Exception e) {}
}
}
class AppendableRunnable<T extends Appendable> implements Runnable {
static long time = 0;
T appendable;
public AppendableRunnable(T appendable){
this.appendable = appendable;
}
@Override
public void run(){
long t0 = System.currentTimeMillis();
for (int j = 0 ; j < 10000 ; j++){
try {
appendable.append("some string");
} catch (IOException e) {}
}
time+=(System.currentTimeMillis() - t0);
}
}
您能否更详细地描述(或提供示例)以说明多线程是如何导致 System.arraycopy
失败的,?!或者线程如何使 invalid data
传递给 System.arraycopy
?!
Can you please describe in more details (or with a sample) to show how multithread cause the System.arraycopy
fails, ?! Or how the threads make invalid data
passed to System.arraycopy
?!
推荐答案
我是这么理解的.您应该退后一步,查看 AbstractStringBuilder
append
方法中 getChars
的调用位置:
This is how I understand it. You should go one step back and look at where getChars
is called from in the AbstractStringBuilder
append
method:
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
ensureCapacity
方法将检查属性 value
是否足够长以存储附加值,如果不是,则相应地调整大小.
The ensureCapacity
method will check that the attribute value
is long enough to store the appended value and if not then it will resize accordingly.
假设有 2 个线程在同一个实例上调用此方法.记住 value
和 count
被两个线程访问.在这个人为的场景中,假设 value
是一个大小为 5 的数组,数组中有 2 个字符,所以 count=2
(如果你查看 length
方法,你会看到它返回 count
).
Lets say 2 threads invoke this method on the same instance. Keep in mind that value
and count
is accessed by both threads. In this contrived scenario, say value
is an array of size 5 and there are 2 characters in the array so count=2
(if you look at the length
method you'll see that it returns count
).
线程 1 调用 append("ABC")
它将调用 ensureCapacityInternal
并且 value
足够大所以它不会调整大小(需要大小5).线程 1 暂停.
Thread 1 invokes append("ABC")
which will call ensureCapacityInternal
and value
is big enough so it is not resized (requires size 5). Thread 1 pauses.
线程 2 调用 append("DEF")
它将调用 ensureCapacityInternal
并且 value
足够大,因此它也不会调整大小(也需要尺寸 5).线程 2 暂停.
Thread 2 invokes append("DEF")
which will call ensureCapacityInternal
and value
is big enough so it is not resized either (also requires size 5). Thread 2 pauses.
线程 1 继续调用 str.getChars
没有问题.然后它调用 count += len
.线程 1 暂停.请注意,value
现在包含 5 个字符,长度为 5.
Thread 1 continues and calls str.getChars
with no problems. It then calls count += len
. Thread 1 pauses. Note that value
now contains 5 characters and is length 5.
线程 2 现在继续并调用 str.getChars
.请记住,它使用与线程 1 相同的 value
和相同的 count
.但是现在,count
已经增加并且可能大于value
即要复制的目标索引大于数组的长度,这会在 str 中调用
.在我们人为的场景中,System.arraycopy
时导致 IndexOutOfBoundsException
.getCharscount=5
和 value
的大小是 5 所以当 System.arraycopy
被调用时,它不能复制到第 6 个长度为 5 的数组的位置.
Thread 2 now continues and calls str.getChars
. Remember that it uses the same value
and same count
as Thread 1. But now, count
has increased and could potentially be greater than the size of value
i.e. the destination index to copy is greater than the length of the array which causes IndexOutOfBoundsException
when invoking the System.arraycopy
within str.getChars
. In our contrived scenario, count=5
and the size of value
is 5 so when System.arraycopy
is called, it cannot copy to the 6th position of an array that's length 5.
这篇关于StringBuilder 在多线程环境中失败的实际原因是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!