Атомарная операция CAS и ее применение в Java

Java

CAS (Compare And Swap) означает сравнение и обмен, CAS — атомарная операция. В операции CAS участвуют три значения: значение V в текущей памяти, значение E в просроченной памяти и значение U, подлежащее обновлению. Если значение V в текущей памяти равно ожидаемому значению E, значение в памяти обновляется до U, и операция CAS завершается успешно. В противном случае операция отказа от обновления CAS завершится ошибкой. CAS широко используется в JUC.Можно сказать, что CAS является основой JUC.Без работы CAS не было бы JUC. CAS часто используется для реализации оптимистичных блокировок в Java.По сравнению с пессимистическими блокировками, синхронизированными блокировками в Java, оптимистические блокировки не требуют приостановки и пробуждения потоков.В случае высокой параллелизма частая приостановка и пробуждение потоков будут влиять на производительность. Чтобы понять операции CAS, необходимо сначала понять оптимистическую блокировку и пессимистическую блокировку, а также разницу между ними.

Пессимистическая блокировка и оптимистичная блокировка

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

悲观锁

И синхронизированные блокировки, и ReentrantLocks в Java являются пессимистическими блокировками.В предыдущей статье были проанализированы различия между различными блокировками в Java.Если вам интересно, то можете посмотреть:Многопоточная безопасность и блокировки в Java.

По сравнению с пессимистическими блокировками оптимистичные блокировки предполагают, что другие потоки не обязательно могут изменять общие переменные, поэтому потокам не нужно блокироваться и ждать. Используйте CAS для обновления общей переменной. Если обновление CAS завершается сбоем, это доказывает, что другие потоки изменили общую переменную, и вы можете повторить попытку в цикле, пока обновление не будет успешным. Поскольку операция CAS не приостанавливает поток и, таким образом, снижает накладные расходы на приостановку и пробуждение потока, эта экономия очень значительна в случае высокой степени параллелизма. Циклическая повторная попытка не приостанавливает поток, но потребляет ресурсы ЦП, поскольку поток должен все время циклически повторять попытки, что также является недостатком оптимистической блокировки CAS.java.util.concurrent.atomicВсе классы Atomic в пакете используют оптимистическую блокировку CAS для обеспечения безопасности потоков. Еще одним недостатком оптимистической блокировки CAS является то, что она не может решить проблему «ABA». Это будет подробно проанализировано позже. Блок-схема оптимистической блокировки на основе CAS выглядит следующим образом:

基于CAS的乐观锁

Работа CAS и ее применение в Java

CAS имеет множество приложений на Java, JUC иjava.util.concurrent.atomicВсе следующие атомарные классы используют CAS.

Операции CAS, предоставляемые Unsafe

Unsafe предоставляет множество базовых функций для Java, среди которых функция CAS в Java реализована через этот класс. Если вам интересно, вы можете проверить мою предыдущую статью о классе Unsafe:Небезопасно в Java.

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

В Unsafe предусмотрены операции CAS Object, int и long. Другие типы должны быть реализованы сами по себе. CAS это атомарная операция. Для обеспечения безопасности потоков, помимо атомарности, есть еще видимость и упорядоченность. И видимость, и упорядочение можно достичь в Java с помощью volatile.

Атомарные классы в Java (java.util.concurrent.atomic)

java.util.concurrent.atomicКлассы в пакете гарантируют потокобезопасность благодаря повторным попыткам volatile+CAS.java.util.concurrent.atomicАтомарные классы в пакете можно разделить на четыре типа:

  1. Атомарные скаляры: AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference
  2. Классы массивов: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
  3. Классы обновления: AtomicLongFieldUpdater, AtomicIntegerFieldUpdater, AtomicReferenceFieldUpdater
  4. Классы составных переменных: AtomicMarkableReference, AtomicStampedReference

Атомарный скалярный класс

В основном мы анализируем, как класс AtomicInteger обеспечивает потокобезопасность:

Значение AtomicInteger изменчиво

public class AtomicInteger extends Number implements java.io.Seriablizable {
    ...
    private volatile int value; // value是volatile的,保证了可见性和有序性
    ...
}

Значение в AtomicInteger изменчиво, а volatile может гарантировать видимость и упорядоченность.

получить операцию

public final int get() {
    return value;
}

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

операция incrementAndGet

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

Операция incrementAndGet сначала увеличивает значение на 1, а затем возвращает новое значение. Этот метод внутренне вызывает метод getAndAddInt Unsafe:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // CAS原子更新+循环重试

    return var5;
}

Видно, что в Unsafe сначала считывается значение в памяти в теле цикла, а затем обновляется CAS.Если обновление CAS прошло успешно, происходит выход.Если обновление не удалось, цикл будет повторяться до тех пор, пока обновление прошло успешно.

В предыдущей статье (Небезопасно в Java) Мы проанализировали операции CAS для трех типов объектов в Unsafe: Object, int и long. AtomicLong реализуется через операцию CAS типа long, предоставляемого Unsafe, AtomicReference реализуется через операцию CAS типа Object, предоставляемого Unsafe, а значение в AtomicBoolean также является типом int, а AtomicBoolean выполняет преобразование в int:

public AtomicBoolean(boolean initialValue) {
    value = initialValue ? 1 : 0;
}

1 означает истину, 0 означает ложь. Поэтому AtomicBoolean также реализуется через операцию CAS типа int, предоставляемую Unsafe.

класс массива

Unsafe предоставляет две основные функции, связанные с массивами:

public native int arrayBaseOffset(Class<?> var1);

public native int arrayIndexScale(Class<?> var1);

AtomicIntegerArray в основном использует эти два метода для реализации операций CAS массива. Вот некоторые из основных функций в AtomicIntegerArray:

private static final Unsafe unsafe = Unsafe.getUnsafe();  
private static final int base = unsafe.arrayBaseOffset(int[].class);  // 获取数组中第一个元素实际地址相对整个数组对象的地址的偏移量
private static final int scale = unsafe.arrayIndexScale(int[].class); // 获取数组中第一个元素所占用的内存空间
private final int[] array;  
public final int get(int i) {  
    return unsafe.getIntVolatile(array, rawIndex(i));  
}  
public final void set(int i, int newValue) {  
    unsafe.putIntVolatile(array, rawIndex(i), newValue);  
}

Принцип реализации AtomicLongArray и AtomicReferenceArray аналогичен принципу AtomicIntegerArray.

обновить класс

AtomicLongFieldUpdater используется для обновленияvolatile longСвойство типа, которое является свойством экземпляра, а не свойством класса, то есть нестатическим свойством, которое можно использовать только для обновления экземпляра объекта. Аналогично AtomicLongFieldUpdater AtomicIntegerFieldUpdater используется для обновленияvolatile intСвойства типа.

AtomicLongFieldUpdater и AtomicIntegerFieldUpdater используются для обновления собственного свойства (int long) объекта, а AtomicReferenceFieldUpdater используется для обновления свойства типа оболочки объекта.

класс составных переменных

Оптимистическая блокировка на основе CAS не может быть решенапроблема "АБА". Основная идея решения проблемы "ABA" состоит в том, чтобы пометить значение. AtomicStampedReference решает проблему "ABA", добавляя штамп к значению.

public class AtomicStampedReference<V> {

    private static class Pair<T> {
        final T reference; // 值
        final int stamp; // 值的戳
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair; // volatile保证可见性和有序性
    ...
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
    ...
    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); // 通过Unsafe的compareAndSwapObject来更新
    }
    ...
}

Суммировать

  1. java.util.concurrent.atomicБезопасность потока гарантируется оптимистичной блокировкой на основе CAS. В сценарии большего количества операций чтения и меньшего количества записей производительность пессимистической блокировки синхронизированной блокировки и ReentrantLock будет лучше.
  2. Большое количество операций Unsafe CAS выполняется в JUC, и Unsafe CAS является основой JUC.
  3. Оптимистическая блокировка на основе CAS не может решить проблему «ABA», а AtomicStampedReference решает проблему «ABA», добавляя штампы.
  4. Оптимистичная блокировка на основе цикла CAS+ потребляет много ресурсов ЦП.