How do I prove programmatically that StringBuilder
is not threadsafe?
我如何以编程方式证明StringBuilder不是线程安全的?
I tried this, but it is not working:
我尝试过这个,但它不起作用:
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();
}
}
3 个解决方案
#1
105
Problem
I am afraid the test you have written is incorrect.
我担心你写的测试是不正确的。
The main requirement is to share the same StringBuilder
instance between different threads. Whereas you are creating a StringBuilder
object for each thread.
主要要求是在不同的线程之间共享相同的StringBuilder实例。而您正在为每个线程创建一个StringBuilder对象。
The problem is that a new Threadsafe()
initialises a new StringBuilder()
:
问题是新的Threadsafe()初始化一个新的StringBuilder():
class Threadsafe {
...
StringBuilder sb = new StringBuilder(str);
...
}
class MyThread1 implements Runnable {
Threadsafe sf = new Threadsafe();
...
}
class MyThread2 implements Runnable {
Threadsafe sf = new Threadsafe();
...
}
Explanation
To prove the StringBuilder
class is not thread-safe, you need to write a test where n
threads (n > 1
) append some stuff to the same instance simultaneously.
为了证明StringBuilder类不是线程安全的,你需要编写一个测试,其中n个线程(n> 1)同时将一些东西附加到同一个实例。
Being aware of the size of all the stuff you are going to append, you will be able to compare this value with the result of builder.toString().length()
:
了解要添加的所有内容的大小,您将能够将此值与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());
Since it is actually not thread-safe, you may have trouble getting the result.
由于它实际上不是线程安全的,因此您可能无法获得结果。
An ArrayIndexOutOfBoundsException
may be thrown because of the char[] AbstractStringBuilder#value
array and the allocation mechanism which was not designed for multithreading use.
由于char [] AbstractStringBuilder#value数组和不是为多线程使用而设计的分配机制,可能会抛出ArrayIndexOutOfBoundsException。
Test
Here is my JUnit 5 test which covers both StringBuilder
and StringBuffer
:
这是我的JUnit 5测试,它涵盖了StringBuilder和StringBuffer:
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
);
}
}
Results
AbstractStringBuilderTest.testStringBuilder:
10000 total, 165 error, 5988 failed, 3847 passed.
AbstractStringBuilderTest.testStringBuffer:
10000 total, 10000 passed.
#2
17
Much simpler:
更简单:
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());
}
There will be no order of the digits (they will not be 012...
and so on), but this is something you don't care about. All you care is that not all the digits from range [0..10]
where added to StringBuilder
.
没有数字的顺序(它们不会是012 ......等等),但这是你不关心的事情。所有你关心的是并非所有来自范围[0..10]的数字都添加到StringBuilder中。
On the other hand if you replace StringBuilder
with StringBuffer
, you will always get 10 elements in that buffer (but out of order).
另一方面,如果用StringBuffer替换StringBuilder,那么总是会在该缓冲区中获得10个元素(但是不按顺序)。
#3
11
Consider the following test.
考虑以下测试。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class NotThreadSafe {
private static final int CHARS_PER_THREAD = 1_000_000;
private static final int NUMBER_OF_THREADS = 4;
private StringBuilder builder;
@Before
public void setUp() {
builder = new StringBuilder();
}
@Test
public void testStringBuilder() throws ExecutionException, InterruptedException {
Runnable appender = () -> {
for (int i = 0; i < CHARS_PER_THREAD; i++) {
builder.append('A');
}
};
ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
futures.add(executorService.submit(appender));
}
for (Future<?> future : futures) {
future.get();
}
executorService.shutdown();
String builtString = builder.toString();
Assert.assertEquals(CHARS_PER_THREAD * NUMBER_OF_THREADS, builtString.length());
}
}
This is intended to prove that StringBuilder
is not thread-safe by proof by contradiction method. When run, it always throws exception like following:
这是为了通过矛盾方法证明StringBuilder不是线程安全的。运行时,它总是抛出异常,如下所示:
java.util.concurrent.ExecutionException: java.lang.ArrayIndexOutOfBoundsException: 73726
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at NotThreadSafe.testStringBuilder(NotThreadSafe.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 73726
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:650)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at NotThreadSafe.lambda$testStringBuilder$0(NotThreadSafe.java:28)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Therefore, StringBuilder
is broken when used by multiple threads.
因此,StringBuilder在被多个线程使用时会被破坏。
#1
105
Problem
I am afraid the test you have written is incorrect.
我担心你写的测试是不正确的。
The main requirement is to share the same StringBuilder
instance between different threads. Whereas you are creating a StringBuilder
object for each thread.
主要要求是在不同的线程之间共享相同的StringBuilder实例。而您正在为每个线程创建一个StringBuilder对象。
The problem is that a new Threadsafe()
initialises a new StringBuilder()
:
问题是新的Threadsafe()初始化一个新的StringBuilder():
class Threadsafe {
...
StringBuilder sb = new StringBuilder(str);
...
}
class MyThread1 implements Runnable {
Threadsafe sf = new Threadsafe();
...
}
class MyThread2 implements Runnable {
Threadsafe sf = new Threadsafe();
...
}
Explanation
To prove the StringBuilder
class is not thread-safe, you need to write a test where n
threads (n > 1
) append some stuff to the same instance simultaneously.
为了证明StringBuilder类不是线程安全的,你需要编写一个测试,其中n个线程(n> 1)同时将一些东西附加到同一个实例。
Being aware of the size of all the stuff you are going to append, you will be able to compare this value with the result of builder.toString().length()
:
了解要添加的所有内容的大小,您将能够将此值与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());
Since it is actually not thread-safe, you may have trouble getting the result.
由于它实际上不是线程安全的,因此您可能无法获得结果。
An ArrayIndexOutOfBoundsException
may be thrown because of the char[] AbstractStringBuilder#value
array and the allocation mechanism which was not designed for multithreading use.
由于char [] AbstractStringBuilder#value数组和不是为多线程使用而设计的分配机制,可能会抛出ArrayIndexOutOfBoundsException。
Test
Here is my JUnit 5 test which covers both StringBuilder
and StringBuffer
:
这是我的JUnit 5测试,它涵盖了StringBuilder和StringBuffer:
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
);
}
}
Results
AbstractStringBuilderTest.testStringBuilder:
10000 total, 165 error, 5988 failed, 3847 passed.
AbstractStringBuilderTest.testStringBuffer:
10000 total, 10000 passed.
#2
17
Much simpler:
更简单:
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());
}
There will be no order of the digits (they will not be 012...
and so on), but this is something you don't care about. All you care is that not all the digits from range [0..10]
where added to StringBuilder
.
没有数字的顺序(它们不会是012 ......等等),但这是你不关心的事情。所有你关心的是并非所有来自范围[0..10]的数字都添加到StringBuilder中。
On the other hand if you replace StringBuilder
with StringBuffer
, you will always get 10 elements in that buffer (but out of order).
另一方面,如果用StringBuffer替换StringBuilder,那么总是会在该缓冲区中获得10个元素(但是不按顺序)。
#3
11
Consider the following test.
考虑以下测试。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class NotThreadSafe {
private static final int CHARS_PER_THREAD = 1_000_000;
private static final int NUMBER_OF_THREADS = 4;
private StringBuilder builder;
@Before
public void setUp() {
builder = new StringBuilder();
}
@Test
public void testStringBuilder() throws ExecutionException, InterruptedException {
Runnable appender = () -> {
for (int i = 0; i < CHARS_PER_THREAD; i++) {
builder.append('A');
}
};
ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
futures.add(executorService.submit(appender));
}
for (Future<?> future : futures) {
future.get();
}
executorService.shutdown();
String builtString = builder.toString();
Assert.assertEquals(CHARS_PER_THREAD * NUMBER_OF_THREADS, builtString.length());
}
}
This is intended to prove that StringBuilder
is not thread-safe by proof by contradiction method. When run, it always throws exception like following:
这是为了通过矛盾方法证明StringBuilder不是线程安全的。运行时,它总是抛出异常,如下所示:
java.util.concurrent.ExecutionException: java.lang.ArrayIndexOutOfBoundsException: 73726
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at NotThreadSafe.testStringBuilder(NotThreadSafe.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 73726
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:650)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at NotThreadSafe.lambda$testStringBuilder$0(NotThreadSafe.java:28)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Therefore, StringBuilder
is broken when used by multiple threads.
因此,StringBuilder在被多个线程使用时会被破坏。