测试StringBuilder和StringBuffer之间的区别

测试StringBuilder和StringBuffer之间的区别

问题描述:

我只是想直观地看到它们之间的区别,所以下面是代码。但它总是失败。有人可以帮助我吗?我也看到了关于这些问题,但他们都没有以编程方式显示其差异。测试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:如果任何人有你的代码显示差异,我会很高兴接受它作为答案。因为我不确定即使修改了我的代码是否可以实现与我的代码的区别。

(而不是顺序从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

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

对于Cyrille所说的+1。我想象(图元< = 32位),为您节省从得到一个ConcurrentModificationException的使用StringBuilder的,你会用,比如,附加到一个List<Integer>

基本上,你有,只有固有的原子类型的数组的性质两个线程,每个100个单独的操作。两人在每次追加之前争夺对象锁定,并在之后释放,每次100次。在每次迭代中获胜的线程将随着循环计数器和testnum增加的(非常)少量时间而随机化。

与您的示例不同的更多示例不一定是排序,但确保在使用StringBuilder时实际考虑所有插入。它没有内部同步,所以完全有可能在这个过程中有一些会被淹没或覆盖。 StringBuffer将通过内部同步来处理这个事情,保证所有的插入操作都正确地进行,但是你需要外部同步,比如上面的Cyrille的例子,为每个线程的整个迭代序列保存一个锁以安全地使用StringBuilder。