Java集合系列之五:StringBuilder爲什麼線程不安全?

引言

其實StringBuilder並不屬於集合,但是爲了不再新開文章系列,就寫在這個文章系列裏面。我們在面試的過程中,有時候面試官會穩點Java基礎的問題,如HashMap的相關問題等等。我們都知道StringBuilder是線程不安全的,那麼如果面試過程中,面試官繼續問它爲什麼是線程不安全的,可能很多人都會卡殼在這個問題上。

  • 源碼分析
  • 總結

一、源碼分析

1、append方法分析
如下所示,StringBuilder以及StringBuffer的源碼大致如下,它們都繼承了AbstractStringBuilder

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
...
}
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
...
}

使用的父類的成員變量如下所示:

//存儲字符串的數據
char[] value;
//已經使用的字符數組數量
int count;

我麼使用StringBuilder以及StringBuffer最常用的就是append方法,如下所示:

//可以追加任意對象
@Override
public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }
//可以追加字符串
@Override
public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

實際上調用的是父類AbstractStringBuilder的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;
    }

從以上代碼可以看出,count += len;並不是原子操作。比如此時count值爲10,len值爲1,兩個線程同時執行到了第七行,拿到的count值都是10,執行完加法運算後將結果賦值給count,所以兩個線程執行完後count值爲11,而不是12。所以說StringBuilder是線程不安全的。

2、append方法分析
我們看回AbstractStringBuilderappend()方法源碼的第五行,ensureCapacityInternal()方法是檢查StringBuilder對象的原char數組的容量能不能盛下新的字符串,如果盛不下就調用Arrays.copyOf方法對char數組進行擴容。

 private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
 public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
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);
    }

二、總結

JDK源碼中存在一些線程不安全的操作,其實都是因爲沒有對應的同步操作來保證原子操作。所以我們可以通過此來進行分析,同時可以進行適當的優化和改進。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章