测试StringBuilder和StringBuffer之间的区别

时间:2013-03-08 04:41:15

标签: java stringbuilder stringbuffer

我只想在视觉上看到它们之间的区别,所以下面是代码。但它总是失败。有人可以帮我这个吗?我也见过关于SO的问题,但没有一个以编程方式显示出差异。

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();
     }
}

当我执行此操作时,我有时会以随机的方式获得输出,因此这证明了我的测试。但是,当我甚至用StringBuilder替换StringBuffer并进行测试时,即使它给了我意想不到的输出(而不是顺序,从1到200)。那么有人可以帮助我直观地了解它们之间的区别吗?

P.S:如果有人有您的代码显示差异,我会很乐意接受它作为答案。因为即使修改了代码,我也不确定是否可以与代码实现差异。

3 个答案:

答案 0 :(得分: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);
    }
}

答案 1 :(得分:1)

StringBuffer在方法级别同步。这意味着如果一个线程已经在他的一个方法中,没有人可以输入他的一个方法。但是它并不能保证只要另一个线程使用StringBuilder就会阻塞一个线程使用StringBuilder,因此这两个线程仍将争夺对方法的访问权限,并且你可能随机产生一个无序的结果。 / p>

真正锁定对StringBuffer的访问权限的唯一方法是将访问它的代码放在同步块中:

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

如果你不这样做,那么线程1可以进入sb.append(testnum),线程2将在它的入口处等待,当线程1熄灭时,线程2可能会进入并开始在线程1进入sb.append(“”)之前写入。所以你会看到:

12 13 1415  16 ....

问题是,像这样的锁定也会使StringBuilder也适用。这就是为什么人们可以说StringBuffer上的同步机制是无用的,因此它不再使用的原因(Vector也是如此)。

所以,这样做不能告诉你StringBuilder和StringBuffer之间的区别。 Jon Skeet的回答更好。

答案 2 :(得分:0)

+1表示Cyrille所说的话。我想,只有固有原子类型数组(原语&lt; = 32位)的本质才能避免像使用StringBuilder一样获得ConcurrentModificationException,比如附加到List<Integer>

基本上,你有两个线程,每个100个单独的操作。两者在每次追加之前争夺对象的锁定,然后在每次追加之后释放100次。在每次迭代中获胜的线程将通过增加循环计数器和testnum所花费的(极少)时间来随机化。

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