StringBuilder 在多线程环境中失败的实际原因是什么

编程入门 行业动态 更新时间:2024-10-09 07:18:49
本文介绍了StringBuilder 在多线程环境中失败的实际原因是什么的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

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 and StringBuilder
  • 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 引用的目标数组.复制的组件数等于长度参数.....

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 - 如果复制会导致数据访问超出数组边界.

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.getChars.在我们人为的场景中,count=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 在多线程环境中失败的实际原因是什么

本文发布于:2023-11-25 06:29:01,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1628673.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:多线程   原因   环境   StringBuilder

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!