StringBuilder解析

StringBuilder和StringBuffer的构造器和方法基本相同,StringBuffer是线程安全的,StringBuilder非线程安全。StringBuffer类中的方法添加了synchronized关键字,给方法添加了一个锁,用来保证线程安全。Java9对字符串(String、StringBuffer、StringBuilder)进行了优化,在Java9以前字符串采用char[]数组来保存字符,字符串的每个字符占2字节,Java9的字符串采用byte[]数组和一个encoding-flag字段来保存字符,字符串的每个字符只占1字节,所以Java9的字符串更加节省空间。

StringBuilder源码

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

是什么导致了StringBuilder的线程不安全,进入父类AbstractStringBuilder

    public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
    }

问题在这两行

ensureCapacityInternal(count + len);
putStringAt(count, str); 

ensureCapacityInternal()是一个扩容方法

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        int oldCapacity = value.length >> coder;
        if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }
    }

coder是字符的编码格式,编码默认是Latin1,对应的coder是0。还有一种编码UTF16,对应的coder为1。用count(已经使用的长度)+len(要拼接的长度)得到需要的最小长度minimumCapacity。如果这个长度比原来的容量大,则触发扩容,把原数组复制到一个容量为(minimumCapacity*2+2)的新数组。在并发情况下,可能有多个线程拿到相同count,导致扩容不充分引起数组下标越界异常。一般要三个以上线程同时拿到count且必须是在程序开始时,数组不大的时候才可能出现这个异常。再来看putStringAt()方法

    private final void putStringAt(int index, String str) {
        if (getCoder() != str.coder()) {
            inflate();
        }
        str.getBytes(value, index, coder);
    }

多线程环境下,可能有多个线程同时拿到相同的count,在执行getBytes方法时,这几个线程添加的位置是相同的,可能会发生数据覆盖的情况,示例。

    @Test
    public void stringDemo02() throws InterruptedException {
        CountDownLatch count=new CountDownLatch(10000);
        StringBuilder stringBuilder=new StringBuilder();
        for (int i = 0; i <100 ; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j <100 ; j++) {
                        stringBuilder.append("q");
                        count.countDown();
                    }
                }
            });
            t.start();
        }
        count.await();
        System.out.println(stringBuilder.length());
    }
9912

你可能感兴趣的:(Java基础,java,spring)