1. Предложить задачи синхронизации
Предположим, что мы используем двухъядерный процессор для выполнения двух потоков A и B, Core 1 выполняет поток A и Core 2 выполняет поток B, оба потока теперь должны добавить 1 в переменную элемента I объекта с именем OBJ. Предполагая, что первоначальное значение I составляет 0, теоретически, значение I должно стать 2 после запуска двух потоков, но на самом деле очень вероятно, что результат будет 1.
Давайте проанализируем причины сейчас. Для простоты анализа мы не рассматриваем ситуацию в кеша. Фактически, существует кэш, который увеличит вероятность того, что результат - 1. Поток A считывает переменную I в памяти в арифметическую единицу ядра 1, а затем выполняет операцию добавления, а затем записывает результат расчета обратно в память. Поскольку приведенная выше операция не является атомной операцией, если поток B считывает значение I в памяти перед потоком A записывает значение I, добавив 1 обратно в память (значение I равен 0 в настоящее время), то результат, когда я определенно буду казаться 1. Поскольку значение I читается по темам A и B, а значение после добавления 1 - 1, два потока написают 1, что я.
Наиболее распространенным решением является использование ключевого слова Synchronize для блокировки объекта OBJ с помощью кода, который добавляет 1 к I-видимому коду в двух потоках. Сегодня мы представляем новое решение, которое должно использовать связанные классы в атомном пакете для его решения.
2. Атомическая аппаратная поддержка
В одной системе процессора (Uniprocessor) операции, которые могут быть выполнены в одной инструкции, можно считать «атомными операциями», потому что прерывания могут происходить только между инструкциями (потому что планирование потоков должно быть завершено через прерывания). Это также причина, по которой некоторые системы инструкций процессора вводят TEST_AND_SET, TEST_AND_CLEAR и другие инструкции по критическому ресурсу взаимного исключения. Он отличается в симметричной многопроцессорной структуре, поскольку несколько процессоров работают независимо в системе, даже операции, которые могут быть выполнены в одной инструкции, могут быть нарушены.
На платформе x86 процессор предоставляет средства для блокировки шины во время выполнения инструкции. На чипе процессора есть лидерство #hlockpin. Если префикс «блокировка» добавлена в инструкцию в программе языка сборки, код машины сборки приведет к снижению потенциала #hlockpin при выполнении этой инструкции и отпустит его до конца этой инструкции, тем самым блокируя шину. Таким образом, другие процессоры на той же шине не могут получить доступ к памяти через шину на данный момент, обеспечивая атомацию этой инструкции в многопроцессорной среде. Конечно, не все инструкции можно префикс с блокировкой. Только добавить, ADC и, BTC, Btr, BTS, CMPXCHG, DEC, Inc, NEG, NOT, OR, SBB, Sub, XOR, XADD и XCHG Инструкции могут быть предварительно профиксированы с помощью инструкций «блокировки» для реализации атомных операций.
Основной работой Atomic является CAS (ComparandSet, реализованный с использованием инструкции CMPXCHG, которая является атомной инструкцией). Эта инструкция имеет три операнда, значение памяти v переменной (аббревиатура значения), текущее ожидаемое значение e переменной (аббревиатура исключения), значение u переменной хочет обновить (аббревиатура обновления). Когда значение памяти такое же, как и текущее ожидаемое значение, обновленное значение переменной перезаписывается переменной, а псевдокод выполняется следующим образом.
if (v == e) {v = u return true} else {return false}Теперь мы будем использовать операции CAS для решения вышеуказанных проблем. Поток B считывает переменную I в памяти во временную переменную (при условии, что значение считываемого значения в настоящее время 0), а затем считывает значение I в арифметическую операцию единицы Core1. Затем добавляет 1, чтобы сравнить, является ли значение во временной переменной таким же, как текущее значение i. Если значение I в памяти одинаково со значением результата в операционной единице (то есть I+1) (обратите внимание, что эта часть является операцией CAS, это атомная операция, которая не может быть прервана, и операция CAS в других потоках не может быть выполнена в одно и то же время), в противном случае выполнение инструкции не выполняется. Если инструкция не удается, это означает, что поток A увеличил значение i -1. Для потоков, которые не выполняют операции CAS, если операции CAS выполняются в глупости, это определенно будет успешным. Вы можете видеть, что нет блокировки потока, что по существу отличается от принципа синхронизации.
3. Введение в анализ атомного пакета и исходного кода
Основная особенность класса в атомной пакете заключается в том, что в многопоточной среде, когда несколько потоков работают на одной (включая основные типы и ссылочные типы) одновременно, она является исключительной, то есть, когда несколько потоков обновляют значение переменной одновременно, только один поток может добиться успеха, и безуспешенная нить может продолжать пытаться в подобном блокировке спинового, и в ожидании выполнения выполнения выполнения.
Основные методы в классе атомной серии будут вызывать несколько локальных методов в небезопасном классе. Сначала нам нужно знать, что одна вещь - это небезопасная класс с его полным именем: sun.misc.unsafe. Этот класс содержит большое количество операций на C -коде, включая множество прямых распределений памяти и атомных операционных вызовов. Причина, по которой он отмечается как небезопасность, заключается в том, чтобы сообщить вам, что большое количество вызовов методов в этой области будет иметь риски безопасности и необходимо тщательно использовать, в противном случае это приведет к серьезным последствиям. Например, при распределении памяти через небезопасную, если вы сами указываете определенные области, это может привести к тому, что некоторые указатели, такие как C ++, пересечь границу к другим процессам.
Занятия в атомной пакете можно разделить на 4 группы в соответствии с типом оперативных данных.
AtomicBoolean,AtomicInteger,AtomicLong
Основные типы атомных операций для безопасных потоков
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
Атомная атомная работа типа массива, которая работает не во всем массиве, а на одном элементе в массиве
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
Операции с безопасными потоками на основе основных типов (длинное целое число, целочисленное и эталонное тип) в основных объектах отражения
AtomicReference,AtomicMarkableReference,AtomicStampedReference
Застрахованные эталонные типы и атомные операции эталонных типов, которые предотвращают проблемы с АБА
Как правило, мы используем AtomicInteger, AtomicReference и AtomicStampedReference. Теперь давайте проанализируем исходный код атомного целого числа в атомном пакете. Исходные коды других классов в принципе аналогичны.
1. Конструктор параметров
public atomicinteger (int initiorValue) {value = initialValue;}Как видно из функции конструктора, значение сохраняется в значении переменной элемента
Частное изменчивое значение int;
Значение переменной элемента объявляется как летучий тип, который показывает видимость в нескольких потоках, то есть модификация любого потока будет немедленно видна в других потоках.
2. Метод COMPAREANDSET (значение значения передается через внутреннее это и valueOffet)
Public Final Boolean Compareandeset (int ожидайте, int update) {return uncafe.Этот метод является наиболее основной работой CAS
3. Метод DetAndEntset, в котором вызывается метод сравнения.
public final int getandset (int newvalue) {for (;;) {int current = get (); if (CompareAndset (current, newvalue)) возвратный ток; }}Если другие потоки изменяют значение значения перед выполнением if (CompareandSet (Current, NewValue), значение значения должно отличаться от текущего значения. Если сравнение не выполнится, вы можете только повторно получить значение значения, а затем продолжать сравниваться, пока он не будет успешным.
4. Реализация i ++
public final int getAndIncrement () {for (;;) {int current = get (); int next = current + 1; if (CompareAndset (current, Next)) возврат ток; }}5. Реализация ++ I
public final int ingrementAndget () {for (;;) {int current = get (); int next = current + 1; if (CompareAndset (current, Next)) return далее; }}4. Используйте пример AtomicInteger
В следующей программе используется AtomicInteger для моделирования программы продажи билетов. Эти две программы не будут продавать один и тот же билет в результате выполнения, и они не будут продавать билеты как негативные.
Пакет javaleanning; import java.util.concurrent.atomic.atomicinteger; public class selltickets {atomicinteger билеты = new atomicinteger (100); класс продавец реализует Runnable {@override public run () {while (tictets.get () 0) {int tmp = tickets.get (); if (tickets.compareAndset (tmp, tmp-1)) {system.out.println (thread.currentThread (). getName ()+""+tmp);}}}}} public static void main (String [] args) {selltickets st = new Soldtickets (); State nedteew startew (); stew wordew sellers ();) "Sellera"). Start (); new Thread (St.new seller (), "sellerb"). Start ();}}5. Проблема Аба
Приведенный выше пример запускает результат полностью правильный. Это основано на том факте, что два (или более) потоки работают на данных в том же направлении. В приведенном выше примере оба потока работают на билетах при деклеме. Например, если несколько потоков выполняют операции регистрации объектов в общей очереди, то правильные результаты могут быть получены через класс AtomicReference (на самом деле это относится к очереди, поддерживаемой в AQ). Тем не менее, несколько потоков могут быть зарегистрированы или делитализированы, то есть направление работы данных является непоследовательным, так что ABA может произойти.
Давайте теперь возьмем относительно простой для понимания пример, чтобы объяснить проблему ABA. Предположим, что есть два потока T1 и T2, и эти два потока выполняют операции по укладке и укладку в одном стеке.
Мы используем хвост, определяемый AtomicReference, чтобы сохранить верхнюю позицию стека
AtomicReference <t> хвост;
Предполагая, что поток T1 готов к развертыванию, для операций на укладке, нам нужно только обновить верхнюю позицию стека от SP в Newsp до операции CAS, как показано на рисунке 1. Однако, прежде чем поток T1 выполняет Tail.compareandSet (SP, Newsp), система выполняет планирование потоков, а поток T2 начинает выполнять выполнение. T2 выполняет три операции: A выходит из стека, B выходит из стека, а затем A находится в стеке. В настоящее время система снова начинает планировать, и поток T1 продолжает выполнять операцию по укладкам, но в виде потока T1 элемент в верхней части стека все еще остается (то есть T1 все еще считает, что B все еще является следующим элементом на вершине стека A), а реальная ситуация показана на рисунке 2. стек указывает на узел B. Фактически, B больше не существует в стеке. Результат после того, как T1 ставит из стека, показан на рисунке 3, что, очевидно, не является правильным результатом.
6. Решения проблем ABA
Используйте AtomicmarkableReerference, AtomicStampedReference. Используйте два атомных класса, упомянутые выше, для выполнения операций. При внедрении инструкции по сравнению с сравнением им необходимо не только сравнивать предыдущее значение и ожидаемое значение объекта, но также необходимо сравнить текущее значение (операция) марки и ожидаемое значение (операция) марки. Только когда все то же самое верно, метод сравнения может добиться успеха. Каждый раз, когда обновление будет успешным, значение марки изменяется, а настройка значения марки управляется самим программистом.
public boolean compareandeset (v wederference, v newReference, int weadstamp, int newStamp) {pair <v> current = pair; return wederreference == current.reference && wederstamp == current.stamp && ((newReference == current.Reference && newStamp == current.Stamp) || caspair (current.of (newreference); newstamp);В настоящее время метод сравнения требует четырех параметров: ожидаемый образец, новая сторона, weadsstamp, newstamp. Когда мы используем этот метод, мы должны убедиться, что ожидаемое значение марки не совпадает с значением обновления. Обычно NewStamp = wedingStamp+1
Возьмите вышеупомянутые примеры
Предположим, что поток T1 находится перед стеком: SP указывает на A, а значение марки составляет 100.
Поток T2 выполняет: после выпуска A SP указывает на B, а значение марки становится 101.
После выпуска B SP указывает на C, а значение марки становится 102.
После того, как A помещается в стек, SP указывает на A, а значение марки становится 103.
Поток T1 продолжает выполнять оператор сравнения и обнаруживает, что, хотя SP по -прежнему указывает на A, ожидаемое значение значения марки 100 отличается от текущего значения 103. Следовательно, сравнение не сбои. Вам необходимо получить ценность Newsp (в настоящее время Newsp будет указывать на C), и ожидаемое значение значения гербовой марки 103, а затем снова выполнить операцию сравнения. Таким образом, успешно выкатывает стек, SP будет указывать на C.
Обратите внимание, что, поскольку сравнение может изменить только одно значение одновременно и не может изменить NewReference и NewStamp одновременно, во время реализации, парной класс определяется внутренне для превращения NewReference и NewStamp в один объект. При выполнении операций CAS это на самом деле операция на парном объекте.
Частная статическая пара классов <t> {final t Ссылка; окончательный int mamp; Private Pair (t Ссылка, int mamp) {this.reference = reward; this.Stamp = Stamp; } static <t> pair <t> of (t stragher, int mamp) {return new pare <t> (ссылка, Stamp); }}Для AtomicmarkableReerference значение марки является логической переменной, в то время как значение марки в AtomicStampedReference является целочисленной переменной.
Суммировать
Вышеуказанное касается краткой дискуссии этой статьи о принципах реализации и приложениях атомных пакетов в Java. Я надеюсь, что это будет полезно для всех. Заинтересованные друзья могут продолжать ссылаться на другие связанные темы на этом сайте. Если есть какие -либо недостатки, пожалуйста, оставьте сообщение, чтобы указать это.