本文介绍了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:

  • 比较这些StringBufferStringBuilder
  • 的性能
  • 显示 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.classgetChars 调用 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 个线程在同一个实例上调用此方法.记住 valuecount 被两个线程访问.在这个人为的场景中,假设 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=5value 的大小是 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 在多线程环境中失败的实际原因是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-05 08:03