从 StringBuilder的源码维度去解读为啥导致线程不安全 。
先回顾一下我曾写过的类似 线程不安全的文章:
面试官问 : ArrayList 不是线程安全的,为什么 ?(看完这篇,以后反问面试官)
面试官问 : SimpleDateFormat 不是线程安全的,你有了解过吗?
如果有去看过,你的心,是很明亮的。
(这个线程不安全,肯定又是有哪个公共变量在搞事。)
好的 ,事不宜迟,我们开始。
StringBuilder 线程不安全,直接锁定 append方法 。
源码:
(好了,结束了。 就是这些公共变量搞事情。)
哪里导致不安全?
看到这行代码没 ?
非原子操作 count += len;
简单举个最简单的例子,去看看这个非原子操作的线程并发的场景。
AB两个线程当前count (公共全局变量) 都是 100 , 而 都传入了 str = ‘x’ ,也就是 int len = str.length(); len为1.
这时候 都去执行 count += len;
AB 两个线程 拿到count 都是 100, 然后共同执行 +1, 然后共同拿到结果 count=101 。
这时候,已经出现问题了 。 正常应该是后者拿到count为102 .
所以说,如果线程并发多,不只是AB,而是ABCDEFG, 那 count的值, 都不准成啥样了。。。
那么继续看这个 count += len; 的上面一行
str.getChars(0, len, value, count);
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }
char dst[] 是 传入的 value[]
而dstBegin 就是传入count
简单解释:
正常一个个元素新增, value[] 长度到 9, 那么 count就是10 ,代表dstBegin从 第10个位置新增元素(有序正常进行) 。
想想一种情景。
就是 比如 这个数组当前最大容量是10 ,然后 即将新增元素后本身得到的count是11, 11-10>0
是需要扩容的,
这样扩容之后,这个函数的意义就很对。(保证正常扩容,到了临界点就扩容,这样后面的线程新增元素就包妥了)
但是! 但是!但是!
如果 并发出现了!
也就是 说 后面的B线程本身拿到的count值 是A线程执行完+1的才对!
但是呢,因为并发,AB 线程拿到的count值是一样的,都没触发扩容,相当
于 ensureCapacityInternal这个函数, 都绕过去了, 没扩容 。
所以B线程执行到 str.getChars(0, len, value, count);
这时候就会出现 dst 传入的 value[] 和 dstBegin 传入的count 是一样的。。。
就是说 数组容量塞满了(比如是10),然后你告诉我 下一个新增元素的位置 也是10, 那于是乎,当然就抛出异常 数组越界 ArrayIndexOutOfBoundsException 咯。
好了,该篇就到这。