Оригинальная статья и краткое изложение опыта и жизненные перипетии от набора в школу до фабрики А
Нажмите, чтобы узнать подробностиwww.codercc.com
1. Введение в атомарные операции
Проблемы безопасности параллелизма легко возникают при параллельном программировании. Очень простым примером является многопоточная переменная обновления i = 1. Например, если несколько потоков выполняют операцию i++, правильное значение может быть не получено. Эта проблема, наиболее важно Обычно используемый метод заключается в достижении цели безопасности потоков посредством синхронного управления (О синхронизированных вы можете прочитать в этой статье). Однако, поскольку синхронизированный использует пессимистическую стратегию блокировки, это не особенно эффективное решение. Фактически, пакет atomic в составе J.U.C предоставляет ряд простых, эффективных по производительности и потокобезопасных классов для обновления переменных примитивного типа, элементов массива, ссылочных типов и типов полей в объектах. Все эти классы в пакете atomic используют стратегию оптимистической блокировки для атомарного обновления данных, а в java реализованы с использованием операций CAS.
2. Предварительные знания - работа CAS
Чтобы понять принципы реализации этих классов атомарных операций в пакете atomic, мы должны сначала понять, что такое операции CAS.
Что такое КАС?
При использовании блокировок получение блокировок потоком является пессимистической стратегией блокировки, то есть предполагается, что каждое выполнение кода критической секции вызовет конфликты, поэтому, когда текущий поток получает блокировку, он также блокирует получение блокировки другими потоками. . Операция CAS (также известная как операция без блокировки) представляет собой оптимистическую стратегию блокировки, которая предполагает отсутствие конфликтов при доступе всех потоков к общим ресурсам, а поскольку конфликтов не будет, другие потоки, естественно, не будут блокировать операцию. Поэтому поток не будет заблокирован и остановлен. Так что, если есть конфликт? Операция без блокировки использует CAS (сравнение и обмен), также известный как сравнение и обмен, для определения наличия конфликта между потоками.Если возникает конфликт, текущая операция повторяется до тех пор, пока конфликт не исчезнет.
Процесс работы КАС
Процесс сравнения и обмена CAS можно обычно понимать как CAS(V,O,N), который содержит три значения: фактическое значение, хранящееся в адресе памяти V, ожидаемое значение (старое значение) O и новое значение. значение обновлено Н. Когда V и O одинаковы, то есть старое значение совпадает с фактическим значением в памяти, что указывает на то, что значение не было изменено другими потоками, то есть старое значение O является последним значением в момент времени. присутствует, и естественно может быть присвоено новое значение N. Дайте V. Напротив, V и O не совпадают, указывая на то, что значение было изменено другими потоками, а старое значение O не является значением последней версии, поэтому новое значение N не может быть присвоено V, а V можно вернуть. Когда несколько потоков используют CAS для манипулирования переменной, только один поток успешно обновится, а остальные потерпят неудачу. Неудачные потоки попытаются снова, и, конечно же, вы можете приостановить потоки.
Реализация CAS требует поддержки набора аппаратных инструкций.После JDK1.5 виртуальная машина может быть реализована с помощью инструкции CMPXCHG, предоставленной процессором.
Synchronized VS CAS
Основная проблема ветерана Synchronized (до оптимизации) заключается в следующем: при наличии конкуренции потоков будут проблемы с производительностью, вызванные блокировкой потоков и блокировками пробуждения, потому что это синхронизация с взаимоисключением (блокирующая синхронизация). CAS не является произвольной приостановкой между потоками.При сбое операции CAS будет предпринята определенная попытка вместо трудоемкой операции приостановки и пробуждения, поэтому ее также называют неблокирующей синхронизацией. Это основное различие между ними.
Проблемы с CAS
-
АВА-проблема Здесь есть такая интересная проблема, потому что CAS проверяет, изменилось ли старое значение. Например, старое значение А становится В, а потом становится А. Просто при выполнении CAS проверяется и выясняется, что старое значение не изменилось и по-прежнему является А, а на самом деле изменилось. Решение может следовать методу оптимистической блокировки, обычно используемому в базе данных, и добавление номера версии может решить эту проблему. Исходный путь изменения A->B->A становится 1A->2B->3C.
-
Слишком долгое время отжима
При использовании CAS это неблокирующая синхронизация, то есть поток не будет приостановлен, а будет прокручиваться (не что иное, как бесконечный цикл) для следующей попытки.Если время прокрутки слишком велико, он будет потребляют много производительности. Если JVM может поддерживать инструкцию паузы, предоставляемую процессором, будет определенное повышение эффективности.
3. Примитивы атомарного обновления
Пакет atomic улучшает основные типы инструментов атомарного обновления, в основном это:
- AtomicBoolean: обновить логическое значение атомарным образом;
- AtomicInteger: обновление Integer атомарным способом обновления;
- AtomicLong: обновление Long атомарным образом;
Использование этих классов в основном одинаково.Здесь мы возьмем AtomicInteger в качестве примера, чтобы обобщить часто используемые методы.
- addAndGet(int delta): атомарно добавляет входное значение к исходному значению в экземпляре и возвращает окончательный результат;
- incrementAndGet(): атомарно добавляет 1 к исходному значению в экземпляре и возвращает окончательный добавленный результат;
- getAndSet(int newValue): обновить значение экземпляра до нового значения и вернуть старое значение;
- getAndIncrement(): атомарно увеличивает исходное значение в экземпляре на 1 и возвращает старое значение до автоинкремента;
Есть еще какие-то методы, можно посмотреть API, так что не буду вдаваться в подробности. Для того, чтобы понять принцип реализации AtomicInteger, возьмем в качестве примера метод getAndIncrement, посмотрим на исходный код:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Видно, что этот метод фактически вызывает метод getAndAddInt небезопасного экземпляра, а небезопасный экземпляр получается через статический метод getUnsafe класса Unsafe:
private static final Unsafe unsafe = Unsafe.getUnsafe();
Класс Unsafe находится в пакете sun.misc, а класс Unsafe предоставляет некоторые низкоуровневые операции Классы атомарных операций в пакете atomic в основном реализуются с помощью ряда методов, которые обеспечивают операции CAS, такие как compareAndSwapInt и compareAndSwapLong, предоставляемые Небезопасный класс. В следующем примере используется простой пример, иллюстрирующий использование AtomicInteger:
public class AtomicDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.get());
}
}
输出结果:
1
2
Пример очень простой, то есть создается новый объект atomicInteger, а метод построения atomicInteger заключается в передаче базового типа данных и их инкапсуляции. Для операций с базовыми переменными, таких как автоинкремент, автодекремент, добавление и обновление, atomicInteger также предоставляет соответствующие методы для выполнения этих операций. Однако, поскольку atomicInteger использует операцию CAS, предоставленную UnSafe, чтобы гарантировать потокобезопасность обновлений данных, и поскольку CAS использует оптимистическую стратегию блокировки, этот метод обновления данных также эффективен.
Принцип реализации AtomicLong такой же, как и у AtomicInteger, за исключением того, что один для переменных типа long, а другой для переменных типа int. И как класс обновления логической переменной, класс AtomicBoolean, реализует обновление?compareAndSet
метод, исходный код выглядит следующим образом:
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
Видно, что метод сравнения на самом деле сначала преобразуется в целочисленную переменную от 0,1, а затем реализован методом атомного обновления CompareAndswint для переменных int. Видно, что атомный пакет содержит только методы атомного обновления трех основных типов логических, INT и длинных. Ссылаясь на метод обновления булева, атомное обновление Char, Doule и Float также может быть реализовано аналогичным образом Отказ
4. Атомарный тип массива обновлений
Классы, предоставляемые пакетом atomic, которые могут атомарно обновлять элементы в массиве:
- AtomicIntegerArray: атомарно обновляет элементы в массиве целых чисел;
- AtomicLongArray: Atomic обновляет элементы в массиве длинных целых чисел;
- AtomicReferenceArray: атомарный элемент обновления в массиве ссылочного типа
Использование этих классов одинаково, а наиболее часто используемые методы суммируются с помощью AtomicIntegerArray:
- addAndGet(int i, int delta): добавить элемент с индексом i в массиве к входному значению в атомарном обновлении;
- getAndIncrement(int i): увеличивает элемент с индексом i в массиве на 1 атомарным способом обновления;
- compareAndSet(int i, int expect, int update): обновить элемент в позиции индекса i в массиве.
Можно видеть, что методы AtomicIntegerArray и AtomicInteger в основном одинаковы, за исключением того, что в методе AtomicIntegerArray есть еще один указанный бит индекса массива i. Вот простой пример:
public class AtomicDemo { // private static AtomicInteger atomicInteger = new AtomicInteger(1); private static int[] value = new int[]{1, 2, 3}; private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
скопировать кодpublic static void main(String[] args) { //对数组中索引为1的位置的元素加5 int result = integerArray.getAndAdd(1, 5); System.out.println(integerArray.get(1)); System.out.println(result); }
} 输出结果: 7 2
Элемент в позиции 1 добавляется на 5 через метод getAndAdd, и из результата видно, что элемент с индексом 1 становится 7, а метод возвращает число 2 перед добавлением.
5. Ссылочный тип атомарного обновления
Если вам нужно атомарно обновить переменные ссылочного типа, чтобы обеспечить безопасность потоков, atomic также предоставляет связанные классы:
- AtomicReference: тип ссылки атомарного обновления;
- AtomicReferenceFieldUpdater: Atomic обновляет поля в ссылочных типах;
- AtomicMarkableReference: тип ссылки атомарного обновления с битом метки;
Использование этих классов в основном одинаково.В качестве примера возьмем AtomicReference, чтобы проиллюстрировать основное использование этих классов. Ниже представлена демонстрация
public class AtomicDemo { private static AtomicReference reference = new AtomicReference(); public static void main(String[] args) { User user1 = new User("a", 1); reference.set(user1); User user2 = new User("b",2); User user = reference.getAndSet(user2); System.out.println(user); System.out.println(reference.get()); } static class User { private String userName; private int age;
скопировать кодpublic User(String userName, int age) { this.userName = userName; this.age = age; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", age=" + age + '}'; } }
} 输出结果: User{userName='a', age=1} User{userName='b', age=2}
Сначала объект User1 инкапсулируется с помощью AtomicReference, а затем вызывается метод getAndSet.Как видно из результатов, этот метод атомарно обновит указанный объект пользователя, чтобы он сталUser{userName='b', age=2}
, который возвращает исходный пользовательский объект User{userName='a', age=1}
.
6. Тип поля атомарного обновления
Если вам нужно обновить поле объекта и обеспечить потокобезопасность в случае многопоточности, atomic также предоставляет соответствующий класс атомарной операции:
- AtomicIntegeFieldUpdater: класс целочисленного поля атомарного обновления;
- AtomicLongFieldUpdater: класс длинного поля атомарного обновления;
- AtomicStampedReference: тип ссылки на атомарное обновление, этот метод обновления будет иметь номер версии. И почему в обновлении есть номер версии, чтобы решить проблему ABA CAS;
Для атомарного обновления поля требуется два шага:
- Классы полей атомарного обновления являются абстрактными классами, только через статические методы.
newUpdater
чтобы создать средство обновления, и вам нужно установить класс и свойства, которые вы хотите обновить; - Чтобы обновить свойства класса, вы должны использовать
public volatile
модифицировать;
Методы, предоставляемые этими классами, в основном одинаковы.В качестве примера возьмем AtomicIntegerFieldUpdater, чтобы увидеть конкретное использование:
public class AtomicDemo {
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); public static void main(String[] args) { User user = new User("a", 1); int oldValue = updater.getAndAdd(user, 5); System.out.println(oldValue); System.out.println(updater.get(user)); } static class User { private String userName; public volatile int age; public User(String userName, int age) { this.userName = userName; this.age = age; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", age=" + age + '}'; } }
}
скопировать код
输出结果: 1 6
Как видно из примера, созданиеAtomicIntegerFieldUpdater
создается с помощью статического метода, который он предоставляет,getAndAdd
Метод добавляет введенное значение в указанное поле и возвращает значение до добавления. Исходное значение поля age в пользовательском объекте равно 1. После добавления 5 видно, что значение поля age в пользовательском объекте стало равным 6.