为什么?为什么StringBuilder是线程不安全的?

原因分析

如果你看了StringBuilder或StringBuffer的源代码会说,因为StringBuilder在append操作时并未使用线程同步,而StringBuffer几乎大部分方法都使用了synchronized关键字进行方法级别的同步处理。

上面这种说法肯定是正确的,对照一下StringBuilder和StringBuffer的部分源代码也能够看出来。

StringBuilder的append方法源代码:

@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}

StringBuffer的append方法源代码:

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

对于上面的结论肯定是没什么问题的,但并没有解释是什么原因导致了StringBuilder的线程不安全?为什么要使用synchronized来保证线程安全?如果不是用会出现什么异常情况?

下面我们来逐一讲解。

异常示例

我们先来跑一段代码示例,看看出现的结果是否与我们的预期一致。

@Test
public void test() throws InterruptedException {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
sb.append(“a”);
}
}).start();
}
// 睡眠确保所有线程都执行完
Thread.sleep(1000);
System.out.println(sb.length());
}

上述业务逻辑比较简单,就是构建一个StringBuilder,然后创建10个线程,每个线程中拼接字符串“a”1000次,理论上当线程执行完成之后,打印的结果应该是10000才对。

但多次执行上面的代码打印的结果是10000的概率反而非常小,大多数情况都要少于10000。同时,还有一定的概率出现下面的异常信息“

Exception in thread “Thread-0” java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(String.java:826)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:449)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at com.secbro2.strings.StringBuilderTest.lambda$test$0(StringBuilderTest.java:18)
at java.lang.Thread.run(Thread.java:748)
9007

线程不安全的原因

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。这种情况一旦发生便不会出现预期的结果。

抛异常的原因

回头看异常的堆栈信息,回发现有这么一行内容:

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当中。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
img

给大家的福利

零基础入门

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

同时每个成长路线对应的板块都有配套的视频提供:

在这里插入图片描述

因篇幅有限,仅展示部分资料

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

因篇幅有限,仅展示部分资料

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-H6tkq7UW-1712885808482)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值