8

我知道 StringBuffer 和 StringBuilder 之间的区别。在这里阅读

一般来说,正如javadoc所说,

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

但是,StringBuilder 的 javadoc 也说:

的实例StringBuilder对于多线程使用是不安全的。如果需要这种同步,那么建议使用 {@link java.lang.StringBuffer}

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

4

4 回答 4

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());
        }
    }
}
于 2013-05-20T17:10:27.670 回答
4

简单回答是不。恕我直言,您不会使用 StringBuffer 而不是 StringBuilder 或其他类。使 StringBuffer 线程安全可以使您的代码的线程安全性降低,因为人们错误地认为如果您使用了 StringBuffer,那么您的代码是线程安全的,而事实并非如此。

如果您使用过 StringBuffer,则在某些时候您必须使用同步,尽管大多数开发人员并不总是清楚其中的位置,而且我已经看到许多错误(即使在成熟的库中)没有完成或没有正确完成。最好使用 StringBuilder 并始终如一地在内部进行锁定。

为什么同步的 StringBuffer 从来都不是一个好主意。

StringBuffer 是首选的情况是否真的存在

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

于 2013-05-20T16:33:08.520 回答
3

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

例如,拥有多个将通过网络输出数据的写入器线程怎么样。在这种情况下,他们可能共享一个公共的文本缓冲区并直接写入它,当缓冲区已满时,它可以通过网络发送。

于 2013-05-20T16:13:47.517 回答
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);
            }
        }
    };
}

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

于 2013-05-20T16:55:39.780 回答