如何以编程方式证明 StringBuilder 不是线程安全的?

如何以编程方式证明不是线程安全的?StringBuilder

我试过这个,但它不起作用:

public class Threadsafe {
    public static void main(String[] args) throws InterruptedException {
        long startdate = System.currentTimeMillis();

        MyThread1 mt1 = new MyThread1();
        Thread t = new Thread(mt1);
        MyThread2 mt2 = new MyThread2();
        Thread t0 = new Thread(mt2);
        t.start();
        t0.start();
        t.join();
        t0.join();
        long enddate = System.currentTimeMillis();
        long time = enddate - startdate;
        System.out.println(time);
    }

    String str = "aamir";
    StringBuilder sb = new StringBuilder(str);

    public void updateme() {
        sb.deleteCharAt(2);
        System.out.println(sb.toString());
    }

    public void displayme() {
        sb.append("b");
        System.out.println(sb.toString());
    }
}

class MyThread1 implements Runnable {
    Threadsafe sf = new Threadsafe();

    public void run() {
        sf.updateme();
    }
}

class MyThread2 implements Runnable {
    Threadsafe sf = new Threadsafe();

    public void run() {
        sf.displayme();
    }
}

答案 1

问题

恐怕你写的测试不正确。

主要要求是在不同的线程之间共享相同的 StringBuilder 实例。而您正在为每个线程创建一个 StringBuilder 对象。

问题是 a 初始化 a :new Threadsafe()new StringBuilder()

class Threadsafe {
    ...
    StringBuilder sb = new StringBuilder(str);
    ...
}
class MyThread1 implements Runnable {
    Threadsafe sf = new Threadsafe();
    ...
}
class MyThread2 implements Runnable {
    Threadsafe sf = new Threadsafe();
    ...
}

解释

为了证明 StringBuilder 类不是线程安全的,你需要编写一个测试,其中线程 () 同时将一些东西追加到同一个实例中。nn > 1

知道要追加的所有内容的大小,您将能够将此值与builder.toString().length()的结果进行比较:

final long SIZE = 1000;         // max stream size

final StringBuilder builder = Stream
        .generate(() -> "a")    // generate an infinite stream of "a"
        .limit(SIZE)            // make it finite
        .parallel()             // make it parallel
        .reduce(new StringBuilder(), StringBuilder::append, (b1, b2) -> b1);
                                // put each element in the builder

Assert.assertEquals(SIZE, builder.toString().length());

由于它实际上不是线程安全的,因此您可能难以获得结果。

数组索引OutOfBounds异常可能由于数组和分配机制而引发,而分配机制不是为多线程使用而设计的。char[] AbstractStringBuilder#value

测试

这是我的JUnit 5测试,它涵盖了StringBuilderStringBuffer

public class AbstractStringBuilderTest {

    @RepeatedTest(10000)
    public void testStringBuilder() {
        testAbstractStringBuilder(new StringBuilder(), StringBuilder::append);
    }

    @RepeatedTest(10000)
    public void testStringBuffer() {
        testAbstractStringBuilder(new StringBuffer(), StringBuffer::append);
    }

    private <T extends CharSequence> void testAbstractStringBuilder(T builder, BiFunction<T, ? super String, T> accumulator) {
        final long SIZE = 1000;
        final Supplier<String> GENERATOR = () -> "a";

        final CharSequence sequence = Stream
                .generate(GENERATOR)
                .parallel()
                .limit(SIZE)
                .reduce(builder, accumulator, (b1, b2) -> b1);

         Assertions.assertEquals(
                SIZE * GENERATOR.get().length(),    // expected
                sequence.toString().length()        // actual
         );
    }

}

结果

AbstractStringBuilderTest.testStringBuilder: 
    10000 total, 165 error, 5988 failed, 3847 passed.

AbstractStringBuilderTest.testStringBuffer:
    10000 total, 10000 passed.

答案 2

简单得多:

StringBuilder sb = new StringBuilder();
IntStream.range(0, 10)
         .parallel()
         .peek(sb::append) // don't do this! just to prove a point...
         .boxed()
         .collect(Collectors.toList());

if (sb.toString().length() != 10) {
    System.out.println(sb.toString());
}

数字不会有顺序(它们不会等),但这是你不关心的事情。您所关心的只是,并非所有数字从范围添加到 .012...[0..10]StringBuilder

另一方面,如果替换为 ,则该缓冲区中将始终获得 10 个元素(但不按顺序排列)。StringBuilderStringBuffer