Представьте себе реальный параллельный сценарий, в котором следует использовать StringBuffer, а не StringBuilder?

Я знаю разницу между StringBuffer и StringBuilder. читайте здесь!

И вообще, как говорит javadoc,

По возможности рекомендуется использовать этот класс вместо StringBuffer, поскольку в большинстве реализаций он будет быстрее.

Но в javadoc StringBuilder также говорится:

Экземпляры StringBuilder небезопасны для использования несколькими потоками. Если такая синхронизация требуется, рекомендуется использовать {@link java.lang.StringBuffer}.

Итак, мне интересно, действительно ли существует случай, когда предпочтение отдается StringBuffer? И поскольку изменяемая строка используется в основном в одном потоке, может ли кто-нибудь дать мне параллельный реальный сценарий, в котором предпочтительнее использовать StringBuffer?


person wxl24life    schedule 20.05.2013    source источник
comment
По словам Джошуа Блоха, потокобезопасность — это не явление типа «все или ничего», абсолютное да/нет. Он перечисляет 4 отдельные категории потокобезопасности, которые различаются главным образом степенью, в которой клиенты API должны активно управлять или синхронизироваться. Это также развито Питером Лоури в его ответе. Ни один класс не является островом.   -  person scottb    schedule 20.05.2013


Ответы (4)


Причина, по которой StringBuffer является потокобезопасным, заключается в том, что в те дни, когда была разработана первая версия java API, люди подходили к параллелизму иначе, чем сейчас. Преобладало мнение, что объекты должны быть потокобезопасными, потому что Java поддерживает потоки, и люди могут использовать любой класс JDK в нескольких потоках. Позже, когда Java начали оптимизировать по времени выполнения, стоимость этих ненужных блоков синхронизации стала проблемой, поэтому новые API разрабатывались без синхронизации. Еще позже JVM начала оптимизировать блокировки до такой степени, что неоспоримые блокировки стали практически бесплатными, что сделало все решение спорным.

StringBuffer по-прежнему является потокобезопасным, потому что старый код может полагаться на его потокобезопасность. Это далеко не обычное использование, но возможное.

Например, предположим, что вы пишете приложение к файлу журнала, которое пересылает записи журнала на центральный сервер. Поскольку мы не хотим блокировать вызывающего абонента во время ожидания сетевого ввода-вывода, мы делаем это в выделенном потоке. Другие потоки будут накапливать свои записи журнала в 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());
        }
    }
}
person meriton    schedule 20.05.2013

Простой ответ - НЕТ. ИМХО, нет здравой ситуации, когда вы бы использовали StringBuffer вместо StringBuilder или другого класса. Обеспечение потокобезопасности StringBuffer может сделать ваш код менее потокобезопасным, потому что люди ошибочно предполагают, что если вы использовали StringBuffer, ваш код является потокобезопасным, когда это не так.

Если вы использовали StringBuffer, в какой-то момент вам придется использовать синхронизированный, хотя большинству разработчиков не всегда понятно, где именно, и я видел много багов, где это (даже в зрелых библиотеках) не было сделано или было сделано неправильно. Гораздо лучше, если вы используете StringBuilder и выполняете внутреннюю блокировку последовательно.

Почему синхронизированный StringBuffer никогда не был хорошей идеей.< /а>

случай, когда StringBuffer является предпочтительным, действительно существовал

Есть один вариант использования; у вас есть библиотека, которая принимает только StringBuffer в API. Это плохой дизайн по причине, упомянутой выше, но не идеальная библиотека. ;)

person Peter Lawrey    schedule 20.05.2013

Действительно в любом месте, когда текстовый буфер может быть доступен одновременно.

Например, как насчет наличия нескольких потоков записи, которые будут выводить данные по сети. В этом случае, возможно, они используют общий текстовый буфер и просто пишут в него напрямую, а когда буфер заполнен, его можно отправить по сети.

person greedybuddha    schedule 20.05.2013
comment
Хорошее предложение, но держу пари, что вы не сможете написать это потокобезопасным способом, не используя синхронизацию. Я бы сам использовал BufferedOutputStream. ;) - person Peter Lawrey; 20.05.2013
comment
К счастью для меня, он не хотел, чтобы реализация соответствовала варианту использования;) - person greedybuddha; 20.05.2013
comment
Размышляя над тем, как написать это без синхронизации, теперь я убежден, что смогу это сделать (спасибо, что сегодня утром сделал упражнение для размышлений), если вы дадите мне возможность в конце концов узнать, что буфер не будет записан в больше. Таким образом, идея состоит в том, чтобы просто использовать геттер для получения общего буфера, который можно использовать совместно и в который можно записывать. Если геттер видит, что длина буфера больше некоторого k, он помещает буфер в очередь для записи и возвращает новый буфер. предполагая, что в какой-то момент вы знаете, что первый буфер в очереди не используется, вы золотой - person greedybuddha; 20.05.2013
comment
Вы можете использовать AtomicReference с compareAndSwap, но это не очевидно. Если вы использовали синхронизацию с StringBuilder, вероятно, это будет проще. - person Peter Lawrey; 20.05.2013
comment
Я ни в коем случае не утверждаю, что это лучший подход, просто это было мысленное упражнение :) - person greedybuddha; 20.05.2013

Следующая программа иногда будет генерировать исключения при использовании 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);
            }
        }
    };
}

Являются ли эти программы репрезентативными для проблемы «реального мира» — довольно субъективный вопрос. Я оставлю это суждение на усмотрение публики.

person Mike Clark    schedule 20.05.2013