# 线程不安全的原因 StringBuilder中针对字符串的处理主要依赖两个成员变量char数组value和count。StringBuilder通过对value的不断扩容和count对应的增加来完成字符串的append操作。
// 存储的字符串(通常情况一部分为字符串内容,一部分为默认值)
char[] value;
// 数组已经使用数量
int count;
上面的这两个属性均位于它的抽象父类AbstractStringBuilder中。 如果查看构造方法我们会发现,在创建StringBuilder时会设置数组value的初始化长度。
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
默认是传入字符串长度加16。这就是count存在的意义,因为数组中的一部分内容为默认值。 当调用append方法时会对count进行增加,增加值便是append的字符串的长度,具体实现也在抽象父类中。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
我们所说的线程不安全的发生点便是在append方法中count的“+=”操作。我们知道该操作是线程不安全的,那么便会发生两个线程同时读取到count值为5,执行加1操作之后,都变成6,而不是预期的7。这种情况一旦发生便不会出现预期的结果。 # 抛异常的原因 回头看异常的堆栈信息,<typo id="typo-2537" data-origin="回" ignoretag="true">回</typo>发现有这么一行内容:
at java.lang.String.getChars(String.java:826)
对应的代码就是上面AbstractStringBuilder中append方法中的代码。对应方法的源代码如下:
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);
}
其实异常是最后一行arraycopy时JVM底层发生的。arraycopy的核心操作就是将传入的String对象copy到value当中。 而异常发生的原因是明明value的下标只到6,程序却要访问和操作下标为7的位置,当然就跑异常了。 那么,为什么会超出这么一个位置呢?这与我们上面讲到到的count被少加有关。在执行str.getChars方法之前还需要根据count校验一下当前的value是否使用完毕,如果使用完了,那么就进行扩容。append中对应的方法如下:
ensureCapacityInternal(count + len);
ensureCapacityInternal的具体实现:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
count本应该为7,value长度为6,本应该触发扩容。但因为并发导致count为6,假设len为1,则传递的minimumCapacity为7,并不会进行扩容操作。这就导致后面执行str.getChars方法进行复制操作时访问了不存在的位置,因此抛出异常。 这里我们顺便看一下扩容方法中的newCapacity方法:
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
除了校验部分,最核心的就是将新数组的长度扩充为原来的两倍再加2。把计算所得的新长度作为Arrays.copyOf的参数进行扩容。 # 小结 经过上面的分析,是不是真正了解了StringBuilder的线程不安全的原因?我们在学习和实践的过程中,不仅要知道一些结论,还要知道这些结论的底层原理,更重要的是学会分析底层原理的方法。 ### 最后 **如果大家需要这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档的话,[可以戳这里即可免费获取!](https://gitee.com/vip204888/java-p7)** 祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!! 感谢大家的支持!! ![image.png](https://www.www.zyiz.net/i/ll/?i=img_convert/15d0a2e907e45a28ec1fca8c61801b1c.png) .com/vip204888/java-p7)** 祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!! 感谢大家的支持!! [外链图片转存中...(img-ExStRuJ9-1628220329523)]