Параллельное программирование на Java (2): как обеспечить атомарность общих переменных?

Java

Потокобезопасность — это вопрос, который мы должны в первую очередь четко рассмотреть, когда занимаемся параллельным программированием на Java. Этот класс отлично работает в однопоточной среде, поэтому можем ли мы быть уверены, что он ведет себя правильно в случае многопоточного параллелизма?

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

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

Как заставить себя чувствовать себя в безопасности, я пока не придумал (если есть хороший способ, дайте знать). А вот как сделать класс безопасным в многопоточной среде, есть 3 правила, скажу я вам:

1. Не делитесь переменными состояния между потоками. 2. Измените переменную состояния на неизменяемую. 3. Используйте синхронизацию при доступе к переменным состояния.

Тогда вы можете спросить, что такое переменные состояния?

Давайте сначала посмотрим на класс без переменных состояния.Пример кода выглядит следующим образом.

class Chenmo {
    public void write() {
        System.out.println("我寻了半生的春天,你一笑便是了。");
    }
}

Класс Chenmo не имеет состояния, у него есть только один метод, ни переменных-членов, ни переменных класса. Любой поток, обращающийся к нему, не повлияет на результаты другого потока, поскольку между двумя потоками нет общих переменных состояния. Таким образом, мы можем сделать следующий вывод:Классы без переменных состояния должны быть потокобезопасными..

Затем мы смотрим на класс с переменными состояния. Предположим, что молчание (класс Chenmo) записывает строку слов (write()метод), необходимо провести статистику, чтобы найти издателя, который потребует плату за рукопись. Добавляем статистическое поле в класс Chenmo, пример кода следующий.

class Chenmo {
    private long count = 0;
    public void write() {
        System.out.println("我寻了半生的春天,你一笑便是了。");
        count++;
    }
}

Класс Chenmo может точно подсчитать количество строк в однопоточной среде, но не в многопоточной. Из-за операции приращенияcount++Его можно разделить на три операции: чтение счетчика, увеличение счетчика на 1 и присвоение результата счетчику. При многопоточности синхронизация этих трех операций может быть хаотичной, и конечное значение счетчика будет меньше ожидаемого значения.

PS: Конкретные причины можно рассмотреть в предыдущем разделе "Параллельное программирование на Java (1): подготовка.

Писать нелегко, мы не можем относиться к Сайленту, не так ли? Тогда придумай что-нибудь.

Предполагая, что поток A изменяет переменную count, необходимо запретить потоку B или потоку C использовать эту переменную, чтобы гарантировать, что поток B или поток C находится в модифицированном состоянии потока A при использовании count.

Как это предотвратить? допустимыйwrite()добавить методsynchronizedключевые слова. Пример кода выглядит следующим образом.

class Chenmo {
    private long count = 0;
    public synchronized void write() {
        System.out.println("我寻了半生的春天,你一笑便是了。");
        count++;
    }
}

ключевые словаsynchronizedЭто простейший механизм синхронизации, который гарантирует, что только один поток может выполняться одновременно.write(), что гарантируетcount++Безопасен в многопоточной среде.

При написании параллельных приложений мы должны поддерживать правильный настрой, то есть — сначала следить за тем, чтобы код работал правильно, а затем улучшать производительность кода.

Но, как мы все знаем,synchronizedдорогой доступ между несколькими потокамиwrite()Этот метод является взаимоисключающим. Когда поток B осуществляет доступ, он должен дождаться окончания доступа потока A, что не может отражать основную ценность многопоточности.

java.util.concurrent.atomic.AtomicInteger— это класс Integer, предоставляющий атомарные операции, а предоставляемые им операции сложения и вычитания являются потокобезопасными. Таким образом, мы можем изменить класс Chenmo следующим образом. Пример кода выглядит следующим образом.

class Chenmo {
    private AtomicInteger count = new AtomicInteger(0);
    public void write() {
        System.out.println("我寻了半生的春天,你一笑便是了。");
        count.incrementAndGet();
    }
}

write()метод больше не нуженsynchronizedКлючевые слова остаются синхронизированными, поэтому больше нет необходимости вызывать этот метод взаимоисключающим образом между несколькими потоками, что может в определенной степени повысить эффективность статистики.

Однажды формат статистики сборов за публикацию изменился не только в количестве строк, но и в количестве слов, поэтому классу Chenmo необходимо добавить еще одну переменную-член. Пример кода выглядит следующим образом.

class Chenmo {
    private AtomicInteger lineCount = new AtomicInteger(0);
    private AtomicInteger wordCount = new AtomicInteger(0);
    public void write() {
        String words = "我这一辈子,走过许多地方的路,行过许多地方的桥,看过许多次的云,喝过许多种类的酒,却只爱过一个正当年龄的人。";
        System.out.println(words);
        lineCount.incrementAndGet();
        wordCount.addAndGet(words.length());
    }
}

Как вы думаете, этот код является потокобезопасным?

Оказывается, этот код не является потокобезопасным. Поскольку lineCount и wordCount являются двумя переменными, хотя они потокобезопасны, когда поток A добавляет 1 к lineCount, нельзя гарантировать, что поток B начнет добавлять 1 к lineCount после того, как поток A завершит подсчет wordCount.

Что мы можем сделать по этому поводу? Способ тоже очень простой, пример кода следующий.

class Chenmo {
    private int lineCount = 0;
    private int wordCount = 0;
    public void write() {
        String words = "我这一辈子,走过许多地方的路,行过许多地方的桥,看过许多次的云,喝过许多种类的酒,却只爱过一个正当年龄的人。";
        System.out.println(words);
        
        synchronized (this) {
            lineCount++;
            wordCount++;
        }
    }
}

Заблокируйте код подсчета строк (lineCount++) и подсчета слов (wordCount++), чтобы эти две строки кода были атомарными. Другими словами, когда поток B выполняет статистику, он должен дождаться завершения сбора статистики потоком A перед запуском.

synchronized (lock) {...}Это простой встроенный механизм блокировки, предоставляемый Java для обеспечения атомарности блоков кода. Поток автоматически получает блокировку перед входом в заблокированный блок кода и снимает блокировку при выходе из блока кода, гарантируя, что набор операторов выполняется как неделимая единица.


Предыдущий:Параллельное программирование на Java (1): введение

Следующий:Как гарантировать атомарность общих переменных?

Поиск в WeChat »Тихий король 2"Общественный номер, подпишитесь и ответьте"бесплатное видео"Получите 500 ГБ высококачественных обучающих видео (по категориям).