1. 程式人生 > >String,StringBuffer與StringBuilder的區別|執行緒安全與執行緒不安全

String,StringBuffer與StringBuilder的區別|執行緒安全與執行緒不安全

轉載自https://www.cnblogs.com/xingzc/p/6277581.html侵權刪

String 字串常量
StringBuffer 字串變數(執行緒安全)
StringBuilder 字串變數(非執行緒安全)

 簡要的說, String 型別和 StringBuffer 型別的主要效能區別其實在於 String 是不可變的物件, 因此在每次對 String 型別進行改變的時候其實都等同於生成了一個新的 String 物件,然後將指標指向新的 String 物件,所以經常改變內容的字串最好不要用 String ,因為每次生成物件都會對系統性能產生影響,特別當記憶體中無引用物件多了以後, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。
 而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 物件本身進行操作,而不是生成新的物件,再改變物件引用。所以在一般情況下我們推薦使用 StringBuffer ,特別是字串物件經常改變的情況下。而在某些特別情況下, String 物件的字串拼接其實是被 JVM 解釋成了 StringBuffer 物件的拼接,所以這些時候 String 物件的速度並不會比 StringBuffer 物件慢,而特別是以下的字串物件生成中, String 效率是遠要比 StringBuffer 快的:
 String S1 = “This is only a” + “ simple” + “ test”;
 StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
 你會很驚訝的發現,生成 String S1 物件的速度簡直太快了,而這個時候 StringBuffer 居然速度上根本一點都不佔優勢。其實這是 JVM 的一個把戲,在 JVM 眼裡,這個
 String S1 = “This is only a” + “ simple” + “test”; 其實就是:
 String S1 = “This is only a simple test”; 所以當然不需要太多的時間了。但大家這裡要注意的是,如果你的字串是來自另外的 String 物件的話,速度就沒那麼快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
這時候 JVM 會規規矩矩的按照原來的方式去做


在大部分情況下 StringBuffer > String
StringBuffer
Java.lang.StringBuffer執行緒安全的可變字元序列。一個類似於 String 的字串緩衝區,但不能修改。雖然在任意時間點上它都包含某種特定的字元序列,但通過某些方法呼叫可以改變該序列的長度和內容。
可將字串緩衝區安全地用於多個執行緒。可以在必要時對這些方法進行同步,因此任意特定例項上的所有操作就好像是以序列順序發生的,該順序與所涉及的每個執行緒進行的方法呼叫順序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可過載這些方法,以接受任意型別的資料。每個方法都能有效地將給定的資料轉換成字串,然後將該字串的字元追加或插入到字串緩衝區中。append 方法始終將這些字元新增到緩衝區的末端;而 insert 方法則在指定的點新增字元。
例如,如果 z 引用一個當前內容是“start”的字串緩衝區物件,則此方法呼叫 z.append("le") 會使字串緩衝區包含“startle”,而 z.insert(4, "le") 將更改字串緩衝區,使之包含“starlet”。
在大部分情況下 StringBuilder > StringBuffer

java.lang.StringBuilde
java.lang.StringBuilder一個可變的字元序列是5.0新增的。此類提供一個與 StringBuffer 相容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字串緩衝區被單個執行緒使用的時候(這種情況很普遍)。如果可能,建議優先採用該類,因為在大多數實現中,它比 StringBuffer 要快。兩者的方法基本相同。

 

關於執行緒和執行緒不安全:

 

概述

編輯

如果你的程式碼所在的程序中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和

單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。

或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。

執行緒安全問題都是由全域性變數靜態變數引起的。

若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則的話就可能影響執行緒安全。

安全性

編輯

類要成為執行緒安全的,首先必須在單執行緒環境中有正確的行為。如果一個類實現正確(這是說它符合規格說明的另一種方式),那麼沒有一種對這個類的物件的操作序列(讀或者寫公共欄位以及呼叫公共方法)可以讓物件處於無效狀態,觀察到物件處於無效狀態、或者違反類的任何不可變數、前置條件或者後置條件的情況。

此外,一個類要成為執行緒安全的,在被多個執行緒訪問時,不管執行時環境執行這些執行緒有什麼樣的時序安排或者交錯,它必須仍然有如上所述的正確行為,並且在呼叫的程式碼中沒有任何額外的同步。其效果就是,在所有執行緒看來,對於執行緒安全物件的操作是以固定的、全域性一致的順序發生的。

正確性與執行緒安全性之間的關係非常類似於在描述 ACID(原子性、一致性、獨立性和永續性)事務時使用的一致性與獨立性之間的關係:從特定執行緒的角度看,由不同執行緒所執行的物件操作是先後(雖然順序不定)而不是並行執行的。

舉例

編輯

比如一個 ArrayList 類,在新增一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

單執行緒執行的情況下,如果 Size = 0,新增一個元素後,此元素在位置 0,而且 Size=1;

而如果是在多執行緒情況下,比如有兩個執行緒,執行緒 A 先將元素存放在位置 0。但是此時 CPU 排程執行緒A暫停,執行緒 B 得到執行的機會。執行緒B也向此 ArrayList 新增元素,因為此時 Size 仍然等於 0 (注意哦,我們假設的是新增一個元素是要兩個步驟哦,而執行緒A僅僅完成了步驟1),所以執行緒B也將元素存放在位置0。然後執行緒A和執行緒B都繼續執行,都增加 Size 的值。

那好,我們來看看 ArrayList 的情況,元素實際上只有一個,存放在位置 0,而 Size 卻等於 2。這就是“執行緒不安全”了。

安全程度

編輯

執行緒安全性不是一個非真即假的命題。 Vector 的方法都是同步的,並且 Vector 明確地設計為在多執行緒環境中工作。但是它的執行緒安全性是有限制的,即在某些方法之間有狀態依賴(類似地,如果在迭代過程中 Vector 被其他執行緒修改,那麼由 Vector.iterator() 返回的 iterator會丟擲ConcurrentModificationException)。

對於 Java 類中常見的執行緒安全性級別,沒有一種分類系統可被廣泛接受,不過重要的是在編寫類時儘量記錄下它們的執行緒安全行為。

Bloch 給出了描述五類執行緒安全性的分類方法:不可變、執行緒安全、有條件執行緒安全、執行緒相容和執行緒對立。只要明確地記錄下執行緒安全特性,那麼您是否使用這種系統都沒關係。這種系統有其侷限性 -- 各類之間的界線不是百分之百地明確,而且有些情況它沒照顧到 -- 但是這套系統是一個很好的起點。這種分類系統的核心是呼叫者是否可以或者必須用外部同步包圍操作(或者一系列操作)。下面幾節分別描述了執行緒安全性的這五種類別。

不可變

不可變的物件一定是執行緒安全的,並且永遠也不需要額外的同步[1] 。因為一個不可變的物件只要構建正確,其外部可見狀態永遠也不會改變,永遠也不會看到它處於不一致的狀態。Java 類庫中大多數基本數值類如 Integer 、 String 和 BigInteger 都是不可變的。

需要注意的是,對於Integer,該類不提供add方法,加法是使用+來直接操作。而+操作是不具執行緒安全的。這是提供原子操作類AtomicInteger的原因。

執行緒安全

執行緒安全的物件具有在上面“執行緒安全”一節中描述的屬性 -- 由類的規格說明所規定的約束在物件被多個執行緒訪問時仍然有效,不管執行時環境如何排列,執行緒都不需要任何額外的同步。這種執行緒安全性保證是很嚴格的 -- 許多類,如 Hashtable 或者 Vector 都不能滿足這種嚴格的定義。

有條件的

有條件的執行緒安全類對於單獨的操作可以是執行緒安全的,但是某些操作序列可能需要外部同步。條件執行緒安全的最常見的例子是遍歷由 Hashtable 或者 Vector 或者返回的迭代器 -- 由這些類返回的 fail-fast 迭代器假定在迭代器進行遍歷的時候底層集合不會有變化。為了保證其他執行緒不會在遍歷的時候改變集合,進行迭代的執行緒應該確保它是獨佔性地訪問集合以實現遍歷的完整性。通常,獨佔性的訪問是由對鎖的同步保證的 -- 並且類的文件應該說明是哪個鎖(通常是物件的內部監視器(intrinsic monitor))。

如果對一個有條件執行緒安全類進行記錄,那麼您應該不僅要記錄它是有條件執行緒安全的,而且還要記錄必須防止哪些操作序列的併發訪問。使用者可以合理地假設其他操作序列不需要任何額外的同步。

執行緒相容

執行緒相容類不是執行緒安全的,但是可以通過正確使用同步而在併發環境中安全地使用。這可能意味著用一個 synchronized 塊包圍每一個方法呼叫,或者建立一個包裝器物件,其中每一個方法都是同步的(就像 Collections.synchronizedList() 一樣)。也可能意味著用 synchronized 塊包圍某些操作序列。為了最大程度地利用執行緒相容類,如果所有呼叫都使用同一個塊,那麼就不應該要求呼叫者對該塊同步。這樣做會使執行緒相容的物件作為變數例項包含在其他執行緒安全的物件中,從而可以利用其所有者物件的同步。

許多常見的類是執行緒相容的,如集合類 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 類 Connection 和 ResultSet 。

執行緒對立

執行緒對立類是那些不管是否呼叫了外部同步都不能在併發使用時安全地呈現的類。執行緒對立很少見,當類修改靜態資料,而靜態資料會影響在其他執行緒中執行的其他類的行為,這時通常會出現執行緒對立。執行緒對立類的一個例子是呼叫 System.setOut() 的類