Цель одновременного программирования - заставить программу работать быстрее, но использование параллелизма может не обязательно заставить программу работать быстрее. Преимущества одновременного программирования могут быть отражены только тогда, когда число параллельных программ достигает определенного порядка. Следовательно, имеет смысл говорить о одновременном программировании только при высокой параллелистике. Хотя никаких программ с высоким объемом параллелизма не было разработано, обучение параллелизму должно лучше понять некоторые распределенные архитектуры. Затем, когда объем параллелизма программы не высок, такой как однопоточная программа, эффективность выполнения однопоточной, выше, чем у многопоточной программы. Почему это? Те, кто знаком с операционной системой, должны знать, что процессор реализует многопоточное количество, выделяя срезы времени на каждый поток. Таким образом, когда ЦП переключается с одной задачи на другую, состояние предыдущей задачи будет сохранено. Когда задача выполнена, ЦП будет продолжать выполнять состояние предыдущей задачи. Этот процесс называется переключением контекста.
В Java MultiThreading, синхронизированное ключевое слово летучих ключевых слов играет важную роль. Все они могут реализовать синхронизацию потоков, но как она реализована внизу?
нестабильный
Волатильный может гарантировать только видимость переменных в каждом потоке, но не может гарантировать атомность. Я не буду много говорить о том, как использовать Java Language. Мое предложение - использовать его в любой другой ситуации, за исключением библиотеки классов в пакете java.util.concurrent.atomic. Смотрите эту статью для получения дополнительных объяснений.
Введение
См. Следующий код
пакет org.go; public class go {volatile int i = 0; private void inc () {i ++; } public static void main (string [] args) {go go = new go (); for (int i = 0; i <10; i ++) {new Thread (() -> {for (int j = 0; j <1000; j ++) go.inc ();}). start (); } while (thread.activeCount ()> 1) {think.yield (); } System.out.println (go.i); }} Результат каждого выполнения приведенного выше кода отличается, а выходной номер всегда составляет менее 10000. Это потому, что при выполнении INC () I ++ не является атомной операцией. Возможно, некоторые люди предложили бы использовать Synchronized для Synchronize Inc () или использовать блокировку под пакетом java.util.concurrent.locks для управления синхронизацией потока. Но они не так хороши, как следующие решения:
пакет org.go; импортировать java.util.concurrent.atomic.atomicinteger; открытый класс go {atomicinteger i = new atomicinteger (0); private void inc () {i.getandIncrement (); } public static void main (string [] args) {go go = new go (); for (int i = 0; i <10; i ++) {new Thread (() -> {for (int j = 0; j <1000; j ++) go.inc ();}). start (); } while (thread.activeCount ()> 1) {think.yield (); } System.out.println (go.i); }} В настоящее время, если вы не понимаете реализацию Atomic, вы обязательно подозреваете, что базовый AtomicInteger может быть реализован с использованием замков, поэтому это может быть не эффективным. Так что же, давайте посмотрим.
Внутренняя реализация атомных классов
Будь то AtomicInteger или CONGURRENTLINKEDQUEUE NODE CLASS COMPURRENTLINKEDQUEUE.NODE, они имеют статическую переменную
Частный статический финальный sun.misc.unsafe небезопасно;, этот класс - инкапсуляция Java Sun :: Misc :: Небезопасно, что реализует атомную семантику. Я хочу увидеть основную реализацию. У меня есть исходный код GCC4.8. По сравнению с местным путем очень удобно найти путь к GitHub. Посмотри здесь.
Возьмите пример реализации интерфейса GetAndIncrement ()
Atomicinteger.java
Частный статический окончательный окончательный небезопасный = небезопасно int next = current + 1; if (CompareAndset (current, Next)) возврат ток; }} public final Boolean CompareandEset (int weard, int update) {return uncafe.compareandswapint (это, valueoffset, weals, update); } Обратите внимание на это для Loop, он вернется только в случае успеха Comparandset. В противном случае он всегда будет сравнивать.
Реализация сравнения называется. Здесь я заметил, что реализация Oracle JDK немного отличается. Если вы посмотрите на SRC в рамках JDK, вы увидите, что Oracle JDK вызывает небезопасно GetAndIncrement (), но я считаю, что когда Oracle JDK реализует небезопасно. Java, он должен вызывать только сравнение, потому что сравнение может реализовать атомные операции увеличения, снижения и установки значений.
Небезопасно. Ява
Публичный родной логический сравнение (объект obj, Long Offset, int ожидайте, Int Update);
Реализация C ++ вызвана через JNI.
Natunsafe.cc
jbooleansun :: misc :: uncefe :: comparandswapint (jobject obj, jlong offset, jint way, jint update) {jint *addr = (jint *) ((char *) obj + смещение); return CompareAndswap (addr, ожидайте, обновление);} static inline boolcompareAndswap (volatile jint *addr, jint old, jint new_val) {jboolean result = false; Спинлок замок; if ((result = ( *addr == old))) *addr = new_val; вернуть результат;} Небезопасно :: ComparandsWapint вызывает статическую функцию CompareandWap. CompareAndswap использует Spinlock в качестве блокировки. Спинлок здесь имеет значение Lockguard, который заблокирован во время строительства и выпущенной во время разрушения.
Нам нужно сосредоточиться на спине. Здесь следует убедиться, что Spinlock является истинной реализацией атомных операций до его выпуска.
Что такое спинлок
Spinlock, своего рода занята, ожидая, чтобы приобрести замок ресурса. В отличие от Mutex, блокируя текущую потоку и выпуская ресурсы ЦП, чтобы дождаться необходимых ресурсов, Spinlock не будет входить в процесс приостановки, ожидая выполнения условий, и повторно соблюдать процессор. Это означает, что Spinlock лучше, чем Mutex, только если стоимость ожидания блокировки меньше, чем стоимость переключателя контекста выполнения потока.
Natunsafe.cc
класс spinlock {static volatile obj_addr_t lock; public: spinlock () {while (! compare_and_swap (& lock, 0, 1)) _jv_threadield (); } ~ spinlock () {release_set (& lock, 0); }}; Используйте статическую переменную статическую летучую блокировку OBJ_ADDR_T; В качестве бита флага охранника реализуется через C ++ RAII, поэтому так называемый блокировка на самом деле является блокировкой статической переменной obj_addr_t. Волатильный в C ++ не может гарантировать синхронизацию. Что гарантируется, так это Compare_and_swap, вызванная конструктором, и статическую переменную блокировку. Когда эта переменная блокировки составляет 1, вам нужно подождать; Когда это 0, вы устанавливаете его на 1 через атомную работу, указывая на то, что вы получили блокировку.
Здесь действительно является несчастным случаем использовать статическую переменную, что означает, что все структуры без блокировки имеют одну и ту же переменную (фактически size_t), чтобы различить, добавить ли блокировку. Когда эта переменная установлена на 1, нужно ждать другого спинлока. Почему бы не добавить частную переменную volatile obj_addr_t Block in sun :: misc :: небезопасно и передать его в спинлок в качестве параметра конструктора? Это эквивалентно разделению бита флага для каждого небезопасного. Будет ли эффект лучше?
> Macro haf_sched_yield определяется в Configure, что означает, что если определение не определен во время компиляции, Spinlock называется True Spin Lock.
posix-threads.h
inline void_jv_threadield (void) {#ifdef hast_sched_yield shard_yield ();#endif / * hast_sched_yield * /} Этот Lock.h имеет разные реализации на разных платформах. В качестве примера мы принимаем платформу IA64 (Intel AMD X64). Другие реализации можно увидеть здесь.
ia64/locks.h
typedef size_t obj_addr_t; inline static boolcompare_and_swap (volatile obj_addr_t *addr, obj_addr_t old, obj_addr_t new_val) {return __sync_bool_compare_and_swap (addr, old, new_val); obj_addr_t *addr, obj_addr_t new_val) {__asm__ __volatile __ ("" ::: "память"); *(addr) = new_val;}__SYNC_BOOL_COMPARE_AND_SWAP-это встроенная функция GCC, а инструкция сборки «память» завершает барьера памяти.
Короче говоря, оборудование обеспечивает синхронизацию многоядерного процессора, а реализация небезопасности максимально эффективна. GCC-Java довольно эффективен, я считаю, что Oracle и OpenJDK не будут хуже.
Атомные операции и встроенные атомные операции GCC
Атомная операция
Выражения Java и выражения C ++ не являются атомными операциями, что означает, что вы находитесь в коде:
// Предположим, что я является переменной I ++, общей между потоками;
В многопользованной среде я доступ к атомным и фактически разделен на следующие три операнда:
Компилятор изменяет время выполнения, поэтому результат выполнения не может быть ожидается.
Встроенная атомная операция GCC
GCC имеет встроенные атомные операции, которые были добавлены с 4.1.2. Раньше они были реализованы с использованием встроенной сборки.
введите __SYNC_FETCH_AND_ADD (TYPE *PTR, значение типа, ...) Тип __SYNC_FETCH_AND_SUB (TYPE *PTR, значение типа, ...) Тип __SYNC_FETCH_AND_OR (TYPE *PTR, TYPE Value, ...) Type __SYNC_FETCH_AND_OR (TYPE *PTR, TYPE TIPE и TYPENC_FITCH_AR_F_FER_F_FETCH_AND_OR (type *PTR Значение, ...) Тип __SYNC_FETCH_AND_XOR (тип *PTR, значение типа, ...) Тип __SYNC_FETCH_AND_NAND (TYPE *PTR, значение типа, ...) Тип __SYNC_ADD_AND_FETCH (TYPE *PTR, TYPE VALU (Тип *ptr, значение типа, ...) Тип __SYNC_AND_AND_FETCH (тип *ptr, значение типа, ...) Тип __SYNC_AND_AND_FETCH (тип *PTR, значение типа, ...) Тип __SYNC_XOR_AND_FETCH (тип *PTR, значение ...) Тип __SYNC_NAND_FETCH (тип __SYNC_BOOL_COMPARE_AND_SWAP (TYPE *PTR, TYPE TYPL TYPE NEWVAL, ...) Тип __SYNC_VAL_COMPARE_AND_SWAP (тип *PTR, тип старости типа NewVal, ...) __ SYNC_SYNCHRONIZE (...) Тип __SYNC_LOCK_TEST_AND_SET (type *ptr, type, ...) __SYNC_LOCK_RELEASE (тип *ptr, ...)
Что следует отметить:
Открытые файлы, связанные
Ниже приведены некоторые реализации Atomic Operation OpenJDK9 на GitHub, надеясь помочь тем, кому нужно знать. В конце концов, OpenJDK используется более широко, чем GCC. - Но в конце концов, нет исходного кода для Oracle JDK, хотя говорят, что исходный код между OpenJDK и Oracle очень мал.
Atomicinteger.java
Небезопасно
uncefe.cpp :: uncafe_compareandexchangeobject
oop.inline.hpp :: oopdesc :: atomic_compare_exchange_oop
atomic_linux_x86.hpp :: atomic :: cmpxchg
inline jlong atomic :: cmpxchg (jlong Exchange_value, volatile jlong* dest, jlong compare_value, cmmpxchg_memory_order order) {bool mp = os :: is_mp (); __asm__ __volatile__ (lock_if_mp (%4) "cmpxchgq%1, (%3)": "= a" (Exchange_value): "r" (Exchange_value), "a" (compare_value), "r" (dest), "(mp):" cc "," память "); return Exchange_value;} Здесь нам нужно дать подсказку программистам Java, которые не знакомы с C/C ++. Формат инструкций встроенного сборки заключается в следующем
__asm__ [__volatile __] (шаблон сборки // Шаблон сборки: [Список выходных операций] // Список ввода: [Список операций ввода] // Выходные списки: [Список Clobber]) // Уничтожение списка
%1, %3, %4 в шаблоне сборки соответствует следующему списку параметров {"r" (Exchange_Value), «r» (DEST), «R» (MP)}, а список параметров разделяется запятыми и сортируется от 0. Выходной параметр помещается справа от первой толстойчины, а выходной параметр помещается вправо от 0. Выходной параметр. «r» означает поместить в общий регистр », а« означает регистрацию eax и »=» означает для вывода (написать обратно). Инструкция CMPXCHG подразумевает использование регистров EAX, то есть параметр %2.
Другие детали не будут перечислены здесь. Реализация GCC должна передавать указатель, который будет обменен, и после успешного сравнения значение назначается непосредственно (присваивание неатомного), а атомность гарантируется Spinlock.
Реализация OpenJDK предназначена для обмена указателем и непосредственно назначать значения через инструкцию сборки CMMPXCHGQ, а атомность гарантируется через инструкцию сборки. Конечно, базовый слой спинлока GCC также гарантирован через CMMPXCHGQ.
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.