7

I know the difference between StringBuffer and StringBuilder. read here!

And generally, as the javadoc says,

Where possible, it is recommended that this class be used in preference to StringBuffer as it will be faster under most implementations.

But, the javadoc of StringBuilder also says:

Instances of StringBuilder are not safe for use by multiple threads. If such synchronization is required then it is recommended that {@link java.lang.StringBuffer} be used

So, I am wondering, is the case that StringBuffer is preferred really existed? And as the mutable string is used mostly in a single thread, can anyone give me an concurrent real-world scenario that StringBuffer is preferred?

Community
  • 1
  • 1
wxl24life
  • 573
  • 1
  • 6
  • 13
  • 2
    According to Joshua Bloch, "thread safe" is not an all-or-none, absolute yes/no phenomenon. He enumerates 4 separate categories of thread safeness that are distinguished chiefly by the degree to which clients of the API must proactively manage or synchronize. This is developed by Peter Lawrey in his response as well. No class is an island. – scottb May 20 '13 at 16:58

4 Answers4

7

The reason StringBuffer is thread-safe is that back in the day when the first version of the java api was designed, people approached concurrency differently than nowadays. The prevailing opinion was that objects should be thread safe - because Java supports threads, and people might use any JDK class in multiple threads. Later, when Java was starting to be optimized for execution time, the cost of those needless synchronization blocks started to become a problem, so newer APIs were designed to not be synchronized. Still later, the JVM started to optimize locks to the point that uncontested locks became essentially free, making the entire decision a moot point.

StringBuffer is still thread-safe, because old code might rely on it being thread-safe. That is far from typical use, but conceivable.

For instance, suppose you were writing a logfile appender that forwards log entries to a central server. Since we don't want to block the caller while waiting for network I/O we do that in a dedicated thread. The other threads would accumulate their log entries in a StringBuffer:

class RemoteLogger implements Runnable, Appender {
    final StringBuffer buffer = new StringBuffer();

    void append(String s) {
        buffer.append(s);
    }

    public void run() {
        for (;;) {
            Thread.sleep(100);

            String message = buffer.toString();
            sendToServer(message);
            buffer.delete(0, message.length());
        }
    }
}
meriton
  • 61,876
  • 13
  • 96
  • 163
4

The simple answer is NO. IMHO there is no sane situation where you would use StringBuffer instead of StringBuilder or another class. Making StringBuffer thread safe can make your code less thread safe because people mistakenly assume that if you have used StringBuffer you code is thread safe when this is not the case.

If you have used StringBuffer, at some point you have to use synchronized, although where is not always clear to most developers, and I have seen many bugs where this (even in mature libraries) wasn't done or wasn't done correctly. It is much better you use StringBuilder and do the locking enternally, consistently.

Why a synchronized StringBuffer was never a good idea.

is the case that StringBuffer is preferred really existed

There is one use case; you have a library which only accepts StringBuffer in the API. This is poor design for the reason mentioned above, but not library is perfect. ;)

Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075
  • Unfortunately Java itself has at least one class that only accepts StringBuffer (not StringBuilder), e.g. [Matcher.appendReplacement(StringBuffer, String)](http://docs.oracle.com/javase/6/docs/api/java/util/regex/Matcher.html). – Mike Clark May 20 '13 at 16:49
  • While I agree with the general sentiment, I think you're being a bit too absolute in saying that external synchronization is *always* required. At least, I don't see how the code I posted in my answer requires additional synchronization? – meriton May 20 '13 at 17:27
  • @meriton Joining for all the thread is effectively synchronization all the threads to stop. Can you give an example where you append more than one thing in the thread in a sane manner because this is not obvious for many developers? – Peter Lawrey May 20 '13 at 17:44
  • 3
    Erm, there is no call to `join` in my answer ...? (May you have read Mike's instead?) – meriton May 20 '13 at 18:26
3

Really anywhere when a text buffer might be accessed concurrently.

As an example, how about having multiple writer threads that will be outputting data across a network. In that case maybe they share a common text buffer and just directly write to it and when the buffer is full it can be sent across the network.

greedybuddha
  • 7,109
  • 3
  • 31
  • 46
  • Nice suggestion, but I bet you can't write that in a thread safe manner without having to using synchronized. I would have used a BufferedOutputStream myself. ;) – Peter Lawrey May 20 '13 at 16:34
  • 1
    Luckily for me he didn't want an implementation to go along with the use case ;) – greedybuddha May 20 '13 at 16:37
  • Thinking about a way to write this without synchronize, I'm now convinced I can do it(thanks for making this morning a thought exercise), as long as you give me the ability to know eventually that a buffer won't be written to anymore. So the idea is just to use a getter to get the common buffer which can be shared and written to. If the getter sees the length of the buffer is greater than some k, it places the buffer on a queue to be written, and returns a new buffer. assuming that at some point you know the first buffer on queue is not being used, you are golden – greedybuddha May 20 '13 at 16:56
  • You could use an AtomicReference with compareAndSwap, but it's not obvious. If you used synchronization with StringBuilder it is likely to be simpler. – Peter Lawrey May 20 '13 at 17:41
  • 1
    I'm by no way claiming this is a better approach, just was a thought exercise :) – greedybuddha May 20 '13 at 18:08
2

The following program will sometimes throw exceptions when using StringBuilder, but will never throw an exception when using StringBuffer.

Program:

public class StringBuilderConcurrent {
    static final StringBuilder sb = new StringBuilder(); // shared memory

    public static void main(String[] args) throws Exception {
        int NUM_WRITERS = 300;
        ArrayList<WriterThread> threads = new ArrayList<WriterThread>(NUM_WRITERS);
        for (int i = 0; i < NUM_WRITERS; i++) {
            WriterThread wt = new WriterThread("writerThread" + i);
            threads.add(wt);
            wt.start();
        }
        for (int i = 0; i < threads.size(); i++) {
            threads.get(i).join();
        }    
        System.out.println(sb);
    }

    public static class WriterThread extends Thread {
        public WriterThread(String name) {
            super(name);
        }
        public void run() {
            String nameNl = this.getName() + "\n";
            for (int i = 1; i < 20; i++) {
                sb.append(nameNl);
            }
        }
    };
}

Because StringBuilder (sb) is not thread safe, having multiple threads write data to sb may result in sb becoming corrupted (e.g. unexpected null characters, a word's letters interspersed with some other word's letters). It's also possible for sb's internal state to become inconsistent enough that an exception might be thrown:

Exception in thread "writerThread0" java.lang.ArrayIndexOutOfBoundsException
    at java.lang.System.arraycopy(Native Method)
    at java.lang.String.getChars(String.java:854)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:391)
    at java.lang.StringBuilder.append(StringBuilder.java:119)
    at test.StringBuilderConcurrent$WriterThread.run(StringBuilderConcurrent.java:35)

The following program is identical to the first except it uses StringBuffer instead of StringBuilder. It will never encounter an ArrayIndexOutOfBoundsException.

public class StringBufferConcurrent {
    static final StringBuffer sb = new StringBuffer(); // shared memory

    public static void main(String[] args) throws Exception {
        int NUM_WRITERS = 300;
        ArrayList<WriterThread> threads = new ArrayList<WriterThread>(NUM_WRITERS);
        for (int i = 0; i < NUM_WRITERS; i++) {
            WriterThread wt = new WriterThread("writerThread" + i);
            threads.add(wt);
            wt.start();
        }
        for (int i = 0; i < threads.size(); i++) {
            threads.get(i).join();
        }

        System.out.println(sb);
    }

    public static class WriterThread extends Thread {
        public WriterThread(String name) {
            super(name);
        }
        public void run() {
            String nameNl = this.getName() + "\n";
            for (int i = 1; i < 20; i++) {
                sb.append(nameNl);
            }
        }
    };
}

Whether these programs are representative of a "real world" problem is a fairly subjective question. I'll leave that judgement up to the audience.

Mike Clark
  • 9,026
  • 2
  • 35
  • 49