2013-03-08 119 views
0

我只是想直觀地看到它們之間的區別,所以下面是代碼。但它總是失敗。有人可以幫助我嗎?我也看到了關於這些問題,但他們都沒有以編程方式顯示其差異。測試StringBuilder和StringBuffer之間的區別

public class BBDifferencetest { 
    protected static int testnum = 0; 

    public static void testStringBuilder() { 
      final StringBuilder sb = new StringBuilder(); 

      Thread t1 = new Thread() { 

        @Override 
        public void run() { 
          for (int x = 0; x < 100; x++) { 
            testnum++; 
            sb.append(testnum); 
            sb.append(" ");         
          } 
        } 
      }; 
      Thread t2 = new Thread() { 

        public void run() { 

          for (int x = 0; x < 100; x++) { 
            testnum++; 
            sb.append(testnum); 
            sb.append(" "); 
          } 
        } 
      }; 
      t1.start(); 
      t2.start(); 

      try { 
        t1.join(); 
        t2.join(); 
      } catch (InterruptedException e) { 
        e.printStackTrace(); 
      } 

      System.out.println("Result is: " + sb.toString()); 

     } 

    public static void main(String args[]) { 
      testStringBuilder(); 
    } 
} 

當我執行此操作時,我有時會以隨機方式得到輸出,所以這證明了我的測試。但是,當我甚至用StringBuffer替換StringBuilder並測試時,即使它給了我意想不到的輸出(而不是從1到200的順序)。那麼有人能幫助我從視覺上了解這種差異嗎?

P.S:如果任何人有你的代碼顯示差異,我會很高興接受它作爲答案。因爲我不確定即使修改了我的代碼是否可以實現與我的代碼的區別。

回答

3

(而不是順序從1到200)

每個線程正執行一個讀,修改,上testnum寫操作。 本身不是線程安全的。

然後每個線程再次獲取testnum的值以追加它。另一個線程可能已經中斷,然後再次遞增該值。

如果你改變你的代碼:

AtomicInteger counter = new AtomicInteger(); 
... 
sb.append(counter.getAndIncrement()); 

那麼你就更有可能看到你所期望的。

使其更清晰,改變你的循環只調用一次append,像這樣:

for (int x = 0; x < 100; x++) { 
    sb.append(counter.incrementAndGet() + " "); 
} 

當我這樣做,爲StringBuffer我總是得到「完美」的輸出。對於StringBuilder我有時會輸出這樣的:

97 98 100 102  104 

這裏的兩個線程均出現了同時追加和內容已被搞砸了。

編輯:這裏的一個稍短完整的例子:

import java.util.concurrent.atomic.AtomicInteger; 

public class Test { 

    public static void main(String[] args) throws InterruptedException { 
     final AtomicInteger counter = new AtomicInteger(); 
     // Change to StringBuffer to see "working" output 
     final StringBuilder sb = new StringBuilder(); 
     Runnable runnable = new Runnable() { 
      @Override 
      public void run() { 
       for (int x = 0; x < 100; x++) { 
        sb.append(counter.incrementAndGet() + " "); 
       } 
      } 
     }; 

     Thread t1 = new Thread(runnable); 
     Thread t2 = new Thread(runnable); 
     t1.start(); 
     t2.start(); 
     t1.join(); 
     t2.join(); 
     System.out.println(sb); 
    } 
} 
+0

對不起,此測試不起作用。你能否以另一種方式提出建議? – Apparatus 2013-03-08 04:53:50

+0

@Sam:「沒有工作」對我來說太過模糊,無法幫助你。請更精確。 – 2013-03-08 04:56:01

+0

我測試了[this](http://pastebin.com/DbrHc2yD)的方式,它沒有顯示出任何區別。在這兩種情況下,我都會在使用StringBuilder和StringBuffer進行測試時獲取非同步輸出。 – Apparatus 2013-03-08 05:01:27

1

StringBuffer的是在方法級別同步。這意味着如果一個線程已經在他的一個方法中,沒有人可以輸入他的一個方法。但它並不能保證只要其他線程使用它就會阻塞一個線程以使用StringBuilder,因此這兩個線程仍然會爭奪對方法的訪問權限,並且您可能會隨機獲得一個無序結果。

真正鎖定到StringBuffer的訪問,唯一的辦法就是把訪問它在一個同步塊中的代碼:

public void run() { 
    synchronized(sb) { 
     for (int x = 0; x < 100; x++) { 
      testnum++; 
      sb.append(testnum); 
      sb.append(" ");         
     } 
    } 
} 

如果你不這樣做,那麼線程1可以進入某人.append(testnum)並且線程2將在其入口處等待,並且當線程1出去時,線程2可能進入並在線程1進入sb.append(「」)之前開始寫入。所以你會看到:

12 13 1415 16 .... 

事情是,像這樣鎖定會使StringBuilder的工作也。這就是爲什麼人們可以說在StringBuffer上的同步機制是無用的,因此爲什麼它不再被使用(對Vector來說同樣的事情)。

所以,這樣做並不能讓你看到StringBuilder和StringBuffer之間的區別。 Jon Skeet的建議更好。

+0

您可否請詳細說明必要的代碼?所以我可以通過用StringBuilder替換StringBuffer來檢查非同步輸出。 – Apparatus 2013-03-08 04:57:14

+0

+1爲你的努力。非常感謝你。 – Apparatus 2013-03-08 05:54:47

0

對於Cyrille所說的+1。我想象(圖元< = 32位),爲您節省從得到一個ConcurrentModificationException的使用StringBuilder的,你會用,比如,附加到一個List<Integer>

基本上,你有,只有固有的原子類型的數組的性質兩個線程,每個100個單獨的操作。兩人在每次追加之前爭奪對象鎖定,並在之後釋放,每次100次。在每次迭代中獲勝的線程將隨着循環計數器和testnum增加的(非常)少量時間而隨機化。

與您的示例不同的更多示例不一定是排序,但確保在使用StringBuilder時實際考慮所有插入。它沒有內部同步,所以完全有可能在這個過程中有一些會被淹沒或覆蓋。 StringBuffer將通過內部同步來處理這個事情,保證所有的插入操作都正確地進行,但是你需要外部同步,比如上面的Cyrille的例子,爲每個線程的整個迭代序列保存一個鎖以安全地使用StringBuilder。

相關問題