The question I am asking is related to Difference between StringBuilder and StringBuffer but not the same. I want to see what really happens if a StringBuilder is modified by two threads at the same time.
I wrote the following classes:
public class ThreadTester
{
public static void main(String[] args) throws InterruptedException
{
Runnable threadJob = new MyRunnable();
Thread myThread = new Thread(threadJob);
myThread.start();
for (int i = 0; i < 100; i++)
{
Thread.sleep(10);
StringContainer.addToSb("a");
}
System.out.println("1: " + StringContainer.getSb());
System.out.println("1 length: " + StringContainer.getSb().length());
}
}
public class MyRunnable implements Runnable
{
@Override
public void run()
{
for (int i = 0; i < 100; i++)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
StringContainer.addToSb("b");
}
System.out.println("2: " + StringContainer.getSb());
System.out.println("2 length: " + StringContainer.getSb().length());
}
}
public class StringContainer
{
private static final StringBuffer sb = new StringBuffer();
public static StringBuffer getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
Initially I kept a StringBuffer in the StringContainer. Since StringBuffer is thread-safe, at a time, only one thread can append to it, so the output is consistent - either both threads reported the length of the buffer as 200, like:
1: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
1 length: 200
2: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
2 length: 200
or one of them reported 199 and the other 200, like:
2: abbabababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab
2 length: 199
1: abbababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababa
1 length: 200
The key is that the last thread to complete reports a length of 200.
Now, I changed StringContainer to have a StringBuilder instead of StringBuffer i.e.
public class StringContainer
{
private static final StringBuilder sb = new StringBuilder();
public static StringBuilder getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
I expect some of the writes to be over-written, which is happening. But the contents of the StringBuilder and the lengths do not match sometimes:
1: ababbabababaababbaabbabababababaab
1 length: 137
2: ababbabababaababbaabbabababababaab
2 length: 137
As you can see the printed content has only 34 chars, but the length is 137. Why is this happening?
@Extreme Coders - I just did one more test run:
2: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1 length: 150
2 length: 150
Java version: 1.6.0_45 and I am using eclipse version: Eclipse Java EE IDE for Web Developers. Version: Juno Service Release 2 Build id: 20130225-0426
UPDATE 1: I ran this outside eclipse and now they seem to be matching, but I am getting ArrayIndexOutOfBoundsException sometimes:
$ java -version
java version "1.6.0_27"
OpenJDK Runtime Environment (IcedTea6 1.12.5) (6b27-1.12.5-0ubuntu0.12.04.1)
OpenJDK Server VM (build 20.0-b12, mixed mode)
$ java ThreadTester
1: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
1 length: 123
2: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
2 length: 123
$ java ThreadTester
2: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
2 length: 115
1: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
1 length: 115
$ java ThreadTester
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(String.java:862)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:408)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at StringContainer.addToSb(StringContainer.java:14)
at ThreadTester.main(ThreadTester.java:14)
2: abbbbbbababbbbabbbbababbbbaabbabbbaaabbbababbbbabaabaabaabaaabababaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2 length: 114
The ArrayIndexOutOfBoundsException is also happening when running from eclipse.
UPDATE 2: There are two problems happening. The first problem of the contents of the StringBuilder not matching the length is happening only in Eclipse and not when I run in command line (at least the 100+ times I ran it on command line it never happened).
The second problem with ArrayIndexOutOfBoundsException should be to do with the internal implementation of StringBuilder class, which keeps an array of chars and does an Arrays.copyOf
when it expands the size. But it still beats me how a write is happening before the size is expanded, no matter what the order of execution is.
BTW, I am inclined to agree with @GreyBeardedGeek's answer that this whole exercise is a huge waste of time :-). Sometimes we get to see only the symptoms i.e. the output of some code and wonder what is going wrong. This question declared a priori that two threads are modifying a (very well-known) thread unsafe object.
UPDATE 3: Here is the official answer from Java Concurrency in Practice p. 35:
In the absence of synchronization, the compiler, processor and runtime can do some downright weird things to the order in which operations appear to execute. Attempts to reason about the order in which memory actions "must" happen in insufficiently synchronized multithreaded programs will almost certainly be incorrect.
Reasoning about insufficiently synchronized concurrent programs is prohibitively difficult.
There is also a nice example NoVisibility
in the book on p. 34.
199
and200
and the size of theStringBuilder
is same as the number of the chars. I have ran this multiple times but each time getting the same results – Tillich199
and200
. Next I ran this in BlueJ and it gave variable length on every run. Seems like IntelliJ is better at handling multiple threads than other IDEs. – Tillich