Сердце параллелизма: что такое CAS? Как Java8 оптимизирует CAS?

Java

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

Сегодня я познакомлю вас с тем, как CAS гарантирует атомарность операций и какие оптимизации Java8 внесла в CAS.

синхронизировано: Overkill

Давайте посмотрим на несколько строк кода:

public class CASTest {
    static int i = 0;

    public static void increment() {
        i++;
    }
}

Если 100 потоков вызовут метод increment() для увеличения i одновременно, будет ли результат i равен 100?

Учащиеся, изучавшие многопоточность, должны знать, что этот метод небезопасен для потоков, поскольку i++ не являетсяатомарная операция, так что трудно получить 100.

Вот небольшое объяснение, почему вы не можете получить 100 (если вы это знаете, то можете сразу пропустить) Операция i++ должна быть разделена компьютером на три шага. 1. Прочитайте значение i. 2. Добавьте 1 к i. 3. Запишите результат окончательного i в память. Итак, если поток A считывает значение i i = 0, то поток B также считывает значение i i = 0. Затем A добавляет 1 к i, а затем записывает в память, i = 1 в это время. Сразу после этого B также добавляет 1 к i, в это время i = 1 в потоке B, а затем поток B записывает i в память, в это время i = 1 в памяти. Другими словами, потоки A и B автоматически увеличивают i, но конечный результат равен 1, а не 2.

Так что делать? Стратегия решения обычно заключается в добавлении блокировки к этому методу следующим образом.

public class CASTest {
    static int i = 0;

    public synchronized static void increment() {
        i++;
    }
}

После добавления synchronized не более одного потока может войти в метод increment(). Таким образом, небезопасность потока не произойдет. Если вы не понимаете синхронизацию, вы можете прочитать мою статью:Они полностью синхронизируются (замок смещается от тяжелого к замку)

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

Некоторые люди здесь могут сказать, что синхронизация не сделала много оптимизации после JDK1.6? Да, было сделано много оптимизаций, добавлены смещенные блокировки, облегченные блокировки и т. д. Однако, даже если они будут добавлены, когда многие потоки конкурируют, накладные расходы все равно велики.Если вы мне не верите, см. мое введение. в другой статье:Тщательно понимать синхронизацию (от предвзятых замков до тяжеловесных замков)

CAS: это маленькое дело дано мне

То, что нет другого способа вместо блокировки синхронизированных методов, а для обеспечения метода приращения () является безопасным потоком?

Давайте посмотрим, если я использую следующий метод, могу ли я гарантировать, что приращение будет потокобезопасным? Действуйте следующим образом:

1. Поток считывает из памяти значение i. Если в этот момент значение i равно 0, назовем это значение k, то есть k = 0 в этот момент.

2. Пусть j = k + 1.

3. Сравниваем значение k со значением i в памяти, если они равны, то это означает, что ни один другой поток не модифицировал значение i, и мы записываем значение j (в данный момент 1) в память, если оно не равно ( Это означает, что значение i было изменено другими потоками), мы не записываем значение j в память, а возвращаемся к шагу 1 и продолжаем эти три операции.

Переводится в такой код:

public static void increment() {
    do{
        int k = i;
        int j = k + 1;
    }while (compareAndSet(i, k, j))
}

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

Некоторые могут узнать здесь, третий шаг сравнения этой операции не только читать память, но и относительно сухой, писать в память и другие операции , этот шаг сам по себе не безопасен ах?

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

Для инструкции с длинным английским словом нам нравится называть ее аббревиатурой, поэтому мы вызываем compareAndSet какCASБар.

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

В Java также предусмотрены атомарные классы для таких CAS, например:

  1. AtomicBoolean
  2. AtomicInteger
  3. AtomicLong
  4. AtomicReference

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

public class CASTest {
    static AtomicInteger i = new AtomicInteger(0);

    public static void increment() {
        // 自增 1并返回之后的结果
        i.incrementAndGet();
    }
}

CAS: кто тайно изменил мою ценность

Хотя этот механизм CAS может гарантировать метод increment(), все же есть некоторые проблемы, например, когда поток A собирается выполнить третий шаг, поток B увеличивает значение i на 1, а затем немедленно уменьшает значение i. 1. Затем нить А выполняет третий шаг.В это время нить А считает, что никто не изменил значение i, потому что значение i не изменилось. И это то, что мы обычно говоримАВА-проблема.

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

Займемся контролем версий

Чтобы решить эту проблему ABA, мы можем ввести контроль версий. Например, каждый раз, когда поток изменяет значение ссылки, версия будет обновляться. Хотя два потока содержат одну и ту же ссылку, их версии различны. Таким образом , Мы можем предотвратить проблемы ABA. Класс AtomicStampedReference предоставляется в Java для управления версиями.

Оптимизация CAS в Java8.

Поскольку метод не блокируется с помощью этого механизма CAS, все потоки могут войти в метод increment().Если в этот метод входит слишком много потоков, возникнет проблема: каждый раз, когда есть поток для выполнения На третьем шаге, значение i всегда изменяется, поэтому поток возвращается к первому шагу и начинает все сначала.

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

Чтобы решить эту проблему, Java8 ввел массив ячейки [], и его рабочий механизм выглядит следующим образом: если есть 5 потоков для выполнения операции автоматической приращения на i, поскольку существует не так много потоков, вероятность конфликта Маленький. Затем позвольте им использовать CAS, чтобы увеличить себя так, как они обычно делают.

Однако, если есть 100 потоков для выполнения операции автоинкремента над i, в это время конфликт сильно возрастет, и система распределит эти потоки по разным элементам массива ячеек, если в ячейке[10] 10 элементов. значение инициализации элемента равно 0, тогда система разделит 100 потоков на 10 групп, и каждая группа будет выполнять операцию автоинкремента над одним элементом массива ячеек, так что в итоге значение 10 элементов массива ячеек равно 10, система суммирует значения этих 10 элементов, а затем получает 100, что эквивалентно 100 потокам, выполняющим 100 операций автоинкремента над i.

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

Суммировать

По-прежнему очень важно понимать принцип CAS.Это краеугольный камень AQS, а AQS - краеугольный камень concurrent framework.Если будет время, я напишу статью по AQS.

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

1,как, чтобы больше людей могли увидеть этот контент.

2,Подписывайтесь на меня, пусть будут долгосрочные отношения

3. Подпишитесь на официальный аккаунт »трудолюбивый кодер», в нем более 100 оригинальных статей, а также я поделился большим количеством видео, книжных ресурсов и средств разработки, приветствую ваше внимание и читайте мои статьи как можно скорее.