Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StringBuilder modified by multiple threads

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.

like image 428
arun Avatar asked Jun 08 '13 19:06

arun


People also ask

Can StringBuilder be modified?

StringBuilder objects are like String objects, except that they can be modified. Internally, these objects are treated like variable-length arrays that contain a sequence of characters. At any point, the length and content of the sequence can be changed through method invocations.

Why StringBuilder is not synchronized?

StringBuilder is not synchronized so that it is not thread-safe. By not being synchronized, the performance of StringBuilder can be better than StringBuffer. If we are working in a single-threaded environment, using StringBuilder instead of StringBuffer may result in increased performance.

Are StringBuilder strings identical?

StringBuilder and String are completely different objects.

Why StringBuilder is not thread-safe in Java?

Because StringBuilder is not a synchronized one whereas StringBuffer is a synchronized one. When using StringBuilder in a multithreaded environment multiple threads can acess the StringBuilder object simultaneously and the output it produces can't be predicted hence StringBuilder is not a thread safe...


1 Answers

The behavior of a non-threadsafe class when accessed concurrently by multiple threads is by definition "undefined".

Any attempt to ascertain deterministic behavior in such a case is, IMHO, just a huge waste of time.

like image 149
GreyBeardedGeek Avatar answered Sep 22 '22 14:09

GreyBeardedGeek