gpt4 book ai didi

java - StringBuilder 在多线程环境中失败的实际原因

转载 作者:行者123 更新时间:2023-11-30 06:13:48 29 4
gpt4 key购买 nike

StringBuffer 是同步的,但 StringBuilder 不是!这已在 Difference between StringBuilder and StringBuffer 进行了深入讨论。 .

那里有一个示例代码(由@NicolasZozol 回答),它解决了两个问题:

  • 比较这些 StringBufferStringBuilder 的性能
  • 显示 StringBuilder 在多线程环境中可能会失败。

我的问题是关于第二部分,究竟是什么导致它出错?!当您多次运行代码时,堆栈跟踪显示如下:

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.classgetChars 方法调用 System.arraycopy( value, srcBegin, dst, dstBegin, srcEnd - srcBegin); 根据 System.arraycopy javadoc:

Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. A subsequence of array components are copied from the source array referenced by src to the destination array referenced by dest. The number of components copied is equal to the length argument. ....

IndexOutOfBoundsException - if copying would cause access of data outside array bounds.

为简单起见,我将代码原封不动地粘贴在这里:

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 失败,?!或者线程如何将无效数据传递给System.arraycopy?!

最佳答案

我是这样理解的。您应该退后一步,看看在 AbstractStringBuilder append 方法中调用 getChars 的地方:

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 是否足够长以存储附加值,如果没有,则它会相应地调整大小。

假设 2 个线程在同一实例上调用此方法。请记住,valuecount 由两个线程访问。在这个人为的场景中,假设 value 是一个大小为 5 的数组,并且数组中有 2 个字符,所以 count=2(如果您查看 length 方法,你会看到它返回 count)。

线程 1 调用 append("ABC"),它将调用 ensureCapacityInternal 并且 value 足够大,因此不会调整大小(需要大小5).线程 1 暂停。

线程 2 调用 append("DEF"),它将调用 ensureCapacityInternal 并且 value 足够大,因此它也不会调整大小(也需要尺寸 5)。线程 2 暂停。

线程 1 继续并毫无问题地调用 str.getChars。然后调用 count += len。线程 1 暂停。请注意,value 现在包含 5 个字符,长度为 5。

线程 2 现在继续并调用 str.getChars。请记住,它使用与线程 1 相同的 value 和相同的 count。但是现在,count 增加了并且可能大于线程 1 的大小value 即要复制的目标索引大于数组的长度,这会在 str 中调用 System.arraycopy 时导致 IndexOutOfBoundsException .getChars。在我们设计的场景中,count=5 并且 value 的大小为 5,因此当调用 System.arraycopy 时,它无法复制到第 6 个长度为 5 的数组的位置。

关于java - StringBuilder 在多线程环境中失败的实际原因,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31375718/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com