再谈StringBuilder为什么线程不安全以及带来的问题

1 缘起

比较有意思的是,学习锁消除的过程中,有人讲到StringBuffer在方法内构建,不会被其他方法引用时,StringBuffer的锁会被消除,
于是,顺便看了一下同源的StringBuidler为什么线程不安全,以及为什么多线程不安全,和带来的问题,
有了这篇文章,分享出来,帮助读者轻松应对知识交流与考核。

2 StringBuilder

StringBuilder用于缓存字符串的容器,是StringBuffer的高性能版本,因为,StringBuilder适用于单线程,多线程下无法保证程序正常执行。建议优先使用StringBuilder,多数场景下,效率更高。
StringBuilder继承AbstractStringBuilder,而StringBuffer也是继承该类,所以,StringBuilder和StringBuffer是兼容的。

位置:java.lang.StringBuilder
在这里插入图片描述

2.1 StringBuilder线程不安全是指什么?

StringBuilder的多线程不安全是指程序不能正常执行,即数组越界异常,而不是数据错乱问题。

2.1.1 测试样例

package com.monkey.java_study.lock;

/**
 * 锁消除测试.
 *
 * @author xindaqi
 * @since 2023-06-23 16:04
 */
public class LockEliminateTest {

    static StringBuilder sb2 = new StringBuilder();

    public static void main(String[] args) throws Exception {
        LockEliminateTest lockEliminateTest = new LockEliminateTest();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    sb2.append("test");
                }
            }).start();
        }
        Thread.sleep(100);
        System.out.format(">>>>>>StringBuilder:length:%d\n", sb2.length());
    }
}

2.1.2 测试结果

多线程情况下,StringBuilder出现数组越界,无法正常添加数据,程序异常。
异常信息如下图所示:
在这里插入图片描述

2.2 为什么线程不安全?

结论:因为AbstractStringBuilder中的append方法中使用了全局变量count,多线程无法保证数组正常扩充,因此,出现数组越界的异常。
先从append方法说起,StringBuilder继承AbstractStringBuilder,而StringBuilder中直接使用了父类的append方法。
在这里插入图片描述
既然StringBuilder直接使用了AbstractStringBuilder的append方法,我们直接探究这个父类的方法,源码如下,使用的全局变量count作为数组扩容的参数:ensureCapacityInternal。

在这里插入图片描述
数组扩容系列操作源码如下,如果传入的值大于存储数据的字符数组长度,则拷贝数据到新的数组中,保证数据可以正常存储。
问题就出现在这里,多线程出现数组没有及时扩充,使用str.getChars时,导致数组越界。
现在推演一下:
缓存数据的数组arr长度为10,已占用6个空位,还剩4个空位
即初始count为6,
两个线程T1和T2,均携带4个字符,向数组写入数据,
此时:minimumCapacity=count+len=6+4=10
线程T1:
miniumCapacity-value.length=0不满足扩容条件,
此时数据写入arr,刚好填满,还未执行到count+=len时,
线程T2:
开始执行ensureCapacityInternal,此时,count+len=6+4=10,依旧不会触发扩容,
线程T1:
执行count+=len,即count=10,
T2执行到str.getChars时,count为10,出现数组不够用,导致数组越界。
即:T2执行str.getChars(0, 4, [a1, …, a10], 10)
srcBegin=0
srcEnd=4
dst=[a1, …, a10]
dstBegin=10
在826行出现异常,即Sysgem.arrayCopy
这个时候dst长度为10,而新写入的数据从10开始,因此,无法再写入数据了,抛出异常。

在这里插入图片描述

在这里插入图片描述
多线程出现的时间差,导致数组没有正常被扩容,
最后无法写入数据,数组越界后抛出异常。
测试样例:

char[] dst = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
        String str = "test";
        str.getChars(0, 4, dst, 10);
        System.out.println(">>>>>Char array:" + Arrays.toString(dst));

同款异常如下:
在这里插入图片描述

2.3 StringBuilder多线程不安全带来的问题?

程序无法正常执行(抛出数组越界的异常),而不是数据错乱问题。

3 小结

(1)StringBuilder的多线程不安全是指程序不能正常执行,即数组越界异常,而不是数据错乱问题。
(2)AbstractStringBuilder中的append方法中使用了全局变量count,多线程无法保证数组正常扩充,因此,出现数组越界的异常。
(3)StringBuilde多线程不安全带来的问题是程序无法正常运行。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值