Принцип атомарной работы в JAVA

Java

Когда мы решаем проблемы параллелизма, мы часто используем пакет java.util.concurrent.atomic для Java, так в чем же принцип?

1. Введение

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

  • Используйте блокировки шины, чтобы гарантировать атомарность.Так называемая блокировка шины заключается в использовании сигнала LOCK#, предоставляемого процессором.Когда процессор выводит этот сигнал на шину, запросы других процессоров будут заблокированы, поэтому процессор может использовать только общую память.
  • Используйте блокировки кеша, чтобы гарантировать атомарность.Так называемая блокировка кэша означает, что если область памяти кэшируется в строке кэша процессора и блокируется во время операции блокировки, то при выполнении операции блокировки и обратной записи в память процессор не выставляет сигнал LOCK#. на шине, а вместо этого выставляет на шине сигнал LOCK#, который изменяет адрес внутренней памяти и позволяет механизму когерентности кеша гарантировать атомарность операции, потому что механизм когерентности кеша предотвратит изменение области памяти. данные кэшируются более чем двумя процессорами одновременно Строка кэша становится недействительной, когда данные заблокированной строки кэша заблокированы.

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

Во-вторых, Java-реализация введения атомарных операций.

Атомарные операции могут быть реализованы в Java с помощью блокировок и циклического CAS. Принцип CAS в предыдущей статье"Принцип реализации механизма блокировки JAVA (упрощенный вариант)"Это упоминается во введении к смещенным замкам.

1. Используйте циклический CAS для выполнения атомарных операций.

Давайте рассмотрим пример, основанный на методе безопасного для потоков CAS safeCount и счетчике небезопасных счетчиков.

public class CasAtomicInteger {
    private int i = 0;
    private AtomicInteger atomicI = new AtomicInteger(0);
    public static void main(String[] args) {
        final CasAtomicInteger cas = new CasAtomicInteger();
        List<Thread> ts = new ArrayList<>(600);
        long start = System.currentTimeMillis();
        for (int j = 0; j < 100; j ++) {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 10000; i ++) {
                    cas.count();
                    cas.safeCount();
                }
            });
            ts.add(t);
        }

        for (Thread t : ts) {
            t.start();
        }

        for (Thread t : ts) {
            try {
                t.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("非线程安全count计数器:" + cas.i);
        System.out.println("线程安全safeCount计数器:" + cas.atomicI.get());
        System.out.println(System.currentTimeMillis() - start);
    }

    /**
     * 使用CAS实现线程安全计数器
     */
    private void safeCount() {
        for (;;) {
            int i = atomicI.get();
            boolean suc = atomicI.compareAndSet(i, ++i);
            if (suc) {
                break;
            }
        }
        //        atomicI.addAndGet(1);
    }

    /**
     * 非线程安全计数器
     */
    private void count() {
        i++;
    }
}

Результат данных:

非线程安全count计数器:998883
线程安全safeCount计数器:1000000
107

По-видимому, в счетчике счетчиков, не являющихся потокобезопасными, отсутствуют счетчики. Здесь мы сталкиваемся с похожими проблемами параллелизма, которые можно решить с помощью инструментов из пакета java.util.concurrent.atomic пакета java. Пакет atomic предоставляет некоторые атомарные операции, такие как AtomicBoolean (логическое значение обновляется атомарно), AtomicInteger (значение int обновляется атомарно), AtomicLong (длинное значение обновляется атомарно) и т. д.

2. Говоря о трех основных проблемах атомарной работы CAS

(1) проблема с АБА.Поскольку CAS необходимо проверить, изменилось ли значение при работе со значением, и обновить его, если изменений нет, но если значение изначально было A, стало B, а затем снова изменилось на A, то при использовании CAS для проверки, вы обнаружите, что его значение не меняется, но на самом деле меняется.

Решение: Используйте номер версии и добавляйте к номеру версии 1 при каждом обновлении переменной.Начиная с Java 1.5, для решения проблемы ABA в пакете JDK Atomic предоставляется класс AtomicStampedReference. Роль метода compareAndSet этого класса состоит в том, чтобы сначала проверить, равна ли текущая ссылка ожидаемой ссылке, и проверить, равен ли текущий флаг ожидаемому флагу, и, если все равны, атомарно установить ссылку и значение флага в заданное обновленное значение.

(2) Время цикла велико, а накладные расходы высоки.Если прокрутка CAS будет безуспешной в течение длительного времени, это вызовет очень большие накладные расходы на выполнение ЦП. Если JVM может поддерживать инструкцию паузы, предоставляемую процессором, эффективность в определенной степени повысится.

Инструкция паузы имеет две функции: во-первых, она может задержать выполнение конвейерной инструкции (de-pipeline), чтобы ЦП не потреблял слишком много ресурсов выполнения, время задержки зависит от конкретной версии реализации, а время задержки на некоторых процессорах равен нулю; во-вторых, это позволяет избежать очистки конвейера ЦП, вызванной конфликтом порядка памяти (нарушение порядка памяти) при выходе из цикла, тем самым повышая эффективность выполнения ЦП.

(3)Гарантируются только атомарные операции с общей переменной.При выполнении операций над общей переменной мы можем использовать циклический CAS для обеспечения атомарных операций, но когда работают несколько общих переменных, циклический CAS не может гарантировать атомарность операций, и в это время можно использовать блокировки.

Ссылка: «Искусство параллельного программирования на Java» Фанг Тэнфэй, Вэй Пэн, Ченг Сяомин