想象一下,应该使用StringBuffer而不是StringBuilder的真正并发场景?

时间:2013-05-20 15:40:28

标签: java concurrency thread-safety stringbuilder stringbuffer

我知道StringBuffer和StringBuilder之间的区别。 read here

通常,正如javadoc所说,

  

在可能的情况下,建议首先使用此类优先于StringBuffer,因为在大多数实现中它会更快。

但是,StringBuilder的javadoc也说:

  

StringBuilder的实例不适合多线程使用。如果需要这样的同步,则建议使用{@link java.lang.StringBuffer}

所以,我想知道,StringBuffer首选的情况真的存在吗?由于可变字符串主要用于单个线程,任何人都可以给我一个并行的实际场景,首选StringBuffer吗?

4 个答案:

答案 0 :(得分:7)

StringBuffer是线程安全的原因是,在设计第一版java api的那天,人们以不同于现在的方式接近并发。流行的观点是对象应该是线程安全的 - 因为Java支持线程,人们可能在多个线程中使用任何JDK类。后来,当Java开始针对执行时间进行优化时,那些不必要的同步块的成本开始成为一个问题,因此新的API被设计为不同步。再过一段时间,JVM开始优化锁定,以至于无争议的锁定基本上是免费的,这使整个决策变得没有实际意义。

StringBuffer仍然是线程安全的,因为旧代码可能依赖于它是线程安全的。这远非典型用途,但可以想象。

例如,假设您正在编写将日志条目转发到中央服务器的日志文件追加器。由于我们不想在等待网络I / O时阻塞调用者,因此我们在专用线程中执行此操作。其他线程会在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());
        }
    }
}

答案 1 :(得分:4)

简单的答案是否定的。恕我直言,你不会使用StringBuffer而不是StringBuilder或其他类。使StringBuffer线程安全可以使你的代码更少线程安全,因为人们错误地认为如果你已经使用了StringBuffer,那么在不是这种情况下代码是线程安全的。

如果您已经使用过StringBuffer,那么在某些时候您必须使用synchronized,尽管大多数开发人员并不总是清楚,并且我已经看到很多错误(即使在成熟的库中)没有完成或者没有做得对。使用StringBuilder并在内部进行一致的锁定会好得多。

Why a synchronized StringBuffer was never a good idea.

  

是首选StringBuffer确实存在的情况

有一个用例;你有一个只接受API中的StringBuffer的库。由于上面提到的原因,这是糟糕的设计,但库不是完美的。 ;)

答案 2 :(得分:3)

确实可以同时访问文本缓冲区的任何地方。

作为一个例子,如何让多个编写器线程通过网络输出数据。在这种情况下,它们可能共享一个公共文本缓冲区并直接写入它,当缓冲区已满时,它可以通过网络发送。

答案 3 :(得分:2)

以下程序有时会在使用StringBuilder时抛出异常,但在使用StringBuffer时永远不会抛出异常。

程序:

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);
            }
        }
    };
}

因为StringBuilder(sb)不是线程安全的,所以让多个线程将数据写入sb可能会导致sb被破坏(例如,意外的空字符,一些单词的字母穿插一些其他字母的字母)。 sb的内部状态也可能变得不一致,以至于可能抛出异常:

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)

以下程序与第一个程序完全相同,只是它使用StringBuffer而不是StringBuilder。它永远不会遇到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);
            }
        }
    };
}

这些程序是否代表“现实世界”问题是一个相当主观的问题。我会把这个判断留给听众。