0

我只是想直观地看到它们之间的区别,所以下面是代码。但它总是失败。有人可以帮我吗?我也看到过关于 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();
     }
}

当我执行此操作时,有时会以随机方式获得输出,因此这证明了我的测试。但是当我什至替换StringBuilderStringBuffer测试时,它甚至给了我意想不到的输出(而不是从 1 到 200 的顺序输出)。那么有人可以帮助我从视觉上了解差异吗?

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

4

3 回答 3

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);
    }
}
于 2013-03-08T04:47:20.570 回答
1

StringBuffer 在方法级别同步。这意味着如果线程已经在他的方法之一中,则没有人可以进入他的方法之一。但它并不能保证只要另一个线程使用它,一个线程就会被阻塞使用 StringBuilder,因此两个线程仍然会竞争访问方法,并且您可能会随机得到一个无序的结果。

真正锁定对 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 可能会进入内部并在 Thread 之前开始写入1 进入 sb.append(" ")。所以你会看到:

12 13 1415  16 ....

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

因此,这样做并不能向您展示 StringBuilder 和 StringBuffer 之间的区别。Jon Skeet answer中的建议更好。

于 2013-03-08T04:53:01.113 回答
0

西里尔所说的+1。我想只有固有原子类型(原语 <= 32 位)的数组的性质才能使您免于使用 StringBuilder 获取 ConcurrentModificationException ,例如附加到List<Integer>

基本上,您有两个线程,每个线程有 100 个单独的操作。两者在每次追加之前竞争对象的锁定,并在之后释放它,每个 100 次。在每次迭代中获胜的线程将随机化(非常)少量的时间来增加循环计数器和 testnum。

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

于 2013-03-08T05:17:38.323 回答