Управляемое чтение
JVM内存模型
(JMM) является основой параллелизма.Если вы хотите иметь четкое представление о принципе параллелизма, вы должны иметь глубокое понимание JMM. Я считаю, что большинство моих друзей что-то знают. Я просматривал соответствующий контент за последние два дня, задаваясь вопросом, как выразить это более интуитивно.内存模型
, и имеют относительно глубокое понимание этой модели. Я просто хочу заниматься анимацией в последнее время, поэтому планирую потренировать свои руки и попробовать описать эту модель в виде анимации, кстати, посмотреть, есть ли у меня квалификация, чтобы вырасти до мастера анимации.
С этой целью в этой статье я остановлюсь на следующих аспектах:
- Что такое модель памяти, для чего она нужна и
Java内存模型
как это; -
Java内存模型
как это достигается多线程同步
из; - Общий
同步问题
;
Воюете ли вы с коллегами, одноклассниками, начальством и подчиненными, сверстниками или интервьюерами, каждый будет использовать то, чему он научился всю свою жизнь, и использовать различные средства, чтобы шаг за шагом заставить другую сторону встать на путь капитуляции. в серии вопросов, один за другим, пока он не разделится на высший и низший. при обсужденииJMM
, наиболее распространенными способами разрыва являются следующие:
Если вы хорошо знаете все эти вопросы, то поздравляю, это показывает, что у вас очень прочный фундамент и вы безжалостный человек. Но вы также можете взглянуть на анимации и иллюстрации, которые я тщательно подготовил ниже, и пообщаться друг с другом, чтобы увидеть, есть ли какие-либо ошибки или упущения. Если у вас есть какая-то точка знаний, которую вы не совсем понимаете, вы можете продолжить ее чтение, что может разрешить все ваши затруднения и рассеять туман модели памяти, стоящей за кодом Java. в следующий раз, когда кто-то заговорит с тобойJava内存模型
, оставьте эти вопросы ему.
В этой статье мы обсудим модель памяти Java JMM.
Говоря о JMM, мы должны упомянуть多处理器体系结构
,так же как多线程
.
1. Какая модель памяти
что内存模型
, зачем нужна модель памяти. мы должны高速缓存
вызвал некоторые проблемы.
1.1 Кэш
существует多处理器
В системе процессор обычно имеет один или несколько уровней高速缓存
, что может повысить производительность за счет ускорения доступа к данным (поскольку данные находятся ближе к процессору) и уменьшения трафика на общей шине памяти (поскольку можно выполнить множество операций с памятью).
Но вышеупомянутый процесс приносит много новых проблем, таких как:
Что происходит, когда два процессора одновременно читают и пишут в одну и ту же ячейку памяти? При каких условиях они увидят согласованный контент и как обеспечить согласованность всех кешей?
Чтобы решить проблему согласованности, каждый процессор должен следовать некоторым протоколам при доступе к кешу.При чтении и записи он должен работать в соответствии с протоколом.Связанные протоколы: MSI, MESI, MOSI, Synapse, Firefly, Dragon Протокол и т. д., как показано ниже:
内存模型
Его можно понимать как абстракцию процесса чтения и записи доступа к конкретной памяти или кэшу по определенному протоколу работы, то есть модели памяти.
1.2, модель памяти
На уровне процессора内存模型
Определены необходимые и достаточные условия, чтобы при отображении изменений памяти, сделанных другими процессорами, для текущего процессора разные процессоры имели разные модели памяти:
- Некоторые модели процессорной памяти надежны в том смысле, что все процессоры всегда видят одно и то же значение в любой заданной ячейке памяти;
- Модель памяти других процессоров относительно слаба и нуждается в прохождении через специальный, называемый
内存屏障
(барьеры памяти) для очистки кэша, чтобы записи в кэш были видны другим процессорам, или для аннулирования кэша локального процессора, чтобы кэши, записанные другими процессорами, повторно извлекались. Эти барьеры памяти обычно выполняются при выполнении операций блокировки и разблокировки, и они невидимы для языков высокого уровня. Позже, при объяснении модели памяти Java, мы специально введем барьеры памяти.
2. Модель памяти Java
Давайте начнем с некоторых концептуальных вещей, а затем мы перейдем к картинке позже.
Java内存模型
Описано多线程
Какое поведение допустимо в коде и как потоки взаимодействуют через память. Он описывает взаимосвязь между переменными в программе и низкоуровневыми деталями, которые хранятся и извлекаются между памятью или регистрами в реальной компьютерной системе.
Язык Java предоставляет изменчивые, окончательные и синхронизированныеРазработан, чтобы помочь программистам описать компилятору требования параллелизма программы.
Модель памяти Java определяет поведение энергозависимых и синхронизированных, и, что более важно, обеспечить правильную работу правильно синхронизированных программ Java на всех процессорных архитектурах.
Основные участники модели памяти Java:
变量
: переменные здесь в основном относятся к полям экземпляра, переменным класса и элементам объекта в массиве, исключая локальные переменные и параметры метода (приватный поток);
主内存
: общая основная память, где хранятся переменные; поскольку один поток не может получить доступ к параметрам и локальным переменным другого потока, не имеет значения, считаются ли локальные переменные находящимися в общей основной памяти или рабочей памяти;
工作内存
: у каждого потока есть рабочая память, в которой он хранит рабочую копию переменных, которые он должен использовать или выделять. Когда поток выполняется, он будет продолжать работать с этими рабочими копиями. Основная память содержит основную копию каждой переменной. Существуют правила, когда потоку разрешено или необходимо передавать содержимое рабочей копии своей переменной в главную копию и наоборот;
Java线程
: Статьи, в которых представлены потоки Java, будут подробно объяснены позже.
Затем вы можете нарисовать несколько диаграмм совместной работы участников модели памяти Java:
Механизм потока относится к механизму выполнения JVM.
2.1, атомарная операция модели памяти Java
Взаимодействие между потоками и основной памятью, модель памяти Java определяет 8 операций[1]: эти операции являются атомарными (за исключением типов double и long):
- use: переменные передаются из рабочей памяти в механизм выполнения. Эта операция выполняется каждый раз, когда поток виртуальной машины встречает инструкцию байт-кода, требующую использования значения переменной;
- assign: переменная, которая копирует значение, полученное от механизма выполнения, в рабочую память. Выполняйте эту операцию каждый раз, когда виртуальная машина встречает инструкцию байт-кода, которая присваивает значение переменной;
- read: копирует значение переменной из основной памяти в рабочую память для использования в последующих действиях загрузки;
- load: Поместить значение переменной, полученное из оперативной памяти операцией чтения, в копию рабочей области;
- store: передать значение переменной из рабочей памяти в основную память для последующих операций записи;
- write: Поместить значение переменной, полученное из рабочей памяти операцией сохранения, в переменную в основной памяти;
- lock: пометить переменную как независимую от потока;
- unlock: Освобождение эксклюзивных для потока переменных.
Я думал, как мне лучше объяснить эти 8 операций? Эти 8 операций в основном для переменных, позволяют переменным перемещаться между основной памятью и рабочей памятью и передавать их механизму потока для выполнения.В конце концов, я думаю, что следующий пример кода используется для создания анимации эффект для объяснения этого шага. Фрагмент кода, выполняемый механизмом выполнения:
1public class InterMemoryInteraction { 2 3 public synchronized static void add() { 4 ClassA classA = new ClassA(); 5 classA.var +=2; 6 System.out.println(classA.var); 7 } 8 9 public static void main(String[] args) {10 add();11 }12}1314class ClassA {15 Integer var = 10;16}
Соответствующие инструкции по разборке ключей:
112: getfield #4 // Field com/itzhai/jvm/executeengine/concurrency/ClassA.var:Ljava/lang/Integer;215: invokevirtual #5 // Method java/lang/Integer.intValue:()I318: iconst_2419: iadd520: invokestatic #6 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;623: dup_x1724: putfield #4 // Field com/itzhai/jvm/executeengine/concurrency/ClassA.var:Ljava/lang/Integer;
Выполненные 8 операций можно продемонстрировать с помощью следующей анимации:
Этот анимационный эффект кажется за сто восемьдесят тысяч миль от мастера анимации, но мечта все же есть. Рисовать анимацию непросто, а рисовать анимацию нужно время, поэтому объяснение методов анимации подошло к концу. Если все считают, что это хорошо, ставьте лайк, может скоро выйдет второй сезон...
В документе на Javase8 для удобства понимания эти операции были скорректированы и заменены на следующие операции[4], по сути, базовая модель не изменилась.
Read
: прочитать переменнуюWrite
: написать переменную- Синхронная работа:
Volatile read
: volatile читает переменнуюVolatile write
: volatile записывает переменнуюLock
: блокировка исключительно для переменнойUnlock
: освободить эксклюзивную переменную- Первая или последняя операция потока
- Действие, которое запускает поток или обнаруживает, что поток завершен
Работу этих 8 инструкций также можно описать следующим процессом:
2.2, изменчивая видимость и порядок
Volatile — это самый легкий механизм синхронизации JVM.
Ключевое слово volatile в Java используется в качестве индикатора для компилятора Java и Thread, они не кэшируют значение переменной, оно всегда считывается из основной памяти, поэтому, если вы хотите, чтобы операции чтения и записи в общем экземпляре были атомарными , вы можете объявить их как volatile переменные.
2.2.1, роль летучих
После того, как переменная использует volatile, это означает, что она имеет две характеристики:
2.2.1.1 Видимость: убедитесь, что переменные видны всем потокам
Передача значений переменных между потоками должна осуществляться через основную память: после изменения переменной новое значение синхронизируется обратно в основную память, а значение переменной обновляется из основной памяти до того, как переменная будет прочитана.
Использование volatile гарантирует, что новые значения немедленно синхронизируются с основной памятью и сбрасываются из основной памяти непосредственно перед каждым использованием.
**Как показано ниже:**Дляvolatile
переменная, выполнитьuse
Перед операцией он всегда срабатывает одновременноread
а такжеload
действовать, исполнятьassign
После операции он всегда срабатывает одновременноstore
а такжеwrite
работать:
Но видимость не означает, что это безопасно при параллелизме, рассмотрите код, запустите 20 потоков, каждый поток зацикливается 10 000 раз, чтобы дать изменчивую переменную +1, мы ожидаем, что результат будет 20 000:
1public class VolatileTest { 2 3 public static volatile int race = 0; 4 5 public synchronized static void increase() { 6 race ++; 7 } 8 9 private static final int THREADS_COUNT = 20;1011 public static void main(String[] args) {12 Thread[] threads = new Thread[THREADS_COUNT];13 for (int i = 0; i < THREADS_COUNT; i++) {14 threads[i] = new Thread(() -> {15 for (int j = 0; j < 10000; j++) {16 increase();17 }18 });19 threads[i].start();20 }21 // 等待所有累加线程都结束22 while (Thread.activeCount() > 2)23 Thread.yield();24 System.out.println(race);25 }2627}
Но в итоге мы обнаружили, что результат каждого выполнения не одинаков, но не всегда будет достигать 20000.
Причина в том, что хотя volatile обеспечивает видимость и может быть сразу видна другим потокам после обновления, операция +1 здесь не является атомарной, и она понятнее, если посмотреть на код дизассемблирования:
10: getstatic #2 // Field race:I23: iconst_134: iadd45: putstatic #2 // Field race:I
Строго говоря, даже если дизассемблированный код имеет только одну инструкцию, когда он фактически транслируется в собственный машинный код, он может соответствовать нескольким машинным инструкциям, что означает, что инструкция не обязательно является атомарной операцией.
Как показано на рисунке выше, два потока одновременно выполняют инструкцию getstatic, и оба получают самое последнее значение r (гонка здесь сокращенно r), равное 10.
Предполагая, что поток 1 выполнил все инструкции первым, он запишет окончательное значение 11 из рабочей памяти 1 обратно в переменную r;
Затем поток 2 также выполняет ту же инструкцию, записывая конечное значение 11 в рабочую память 2 обратно в переменную r.
Видно, что значение потока 1 перезаписывается потоком 2.
В заключение
Для операций присваивания, не зависящих от текущего значения, и переменной не нужно участвовать в ограничении неизменяемости с другими переменными состояния, volatile может обеспечить ее атомарность.
Типичное применение: независимо от того, какое текущее состояние переключателя, я хочу включить переключатель сейчас, тогда после операции открытое состояние может быть сразу видно другим потокам.
2.2.1.2, по порядку: запретить перестановку инструкций
Ниже мы рассмотрим классическую задачу блокировки DCL с двойной проверкой.
Чтобы поддерживать ленивую инициализацию и избежать накладных расходов на синхронизацию, мы можем написать проверенный код блокировки следующим образом:
1public class Singleton { 2 3 private static Singleton instance; 4 5 public static Singleton getInstance() { 6 if (instance == null) { 7 synchronized (Singleton.class) { 8 if (instance == null) { 9 instance = new Singleton();10 // 这一句代码实际上会翻译为如下三句11 // reg0 = calloc(sizeof(Singleton));12 // reg0.<init>();13 // instance = reg0;14 }15 }16 }17 return instance;18 }1920 /**21 * hsdis-amd64.dylib https://cloud.tencent.com/developer/article/108267522 * HSDIS是一个Java官方推荐 HotSpot虚拟机JIT编译代码的反汇编插件。我们有了这个插件后,通过JVM参数-XX:+PrintAssembly就可以加载这个HSDIS插件,23 * 然后为我们把JIT动态生成的那些本地代码还原成汇编代码,然后打印出来。24 * @param args25 */26 public static void main(String[] args) {27 // 由于指令编排问题,可能返回空对象28 Singleton.getInstance();29 }3031}
Поскольку приведенный выше комментарий принадлежит оператору для создания синглтонаinstance = new Singleton();
Он будет преобразован в три оператора, и эти три оператора не имеют ограничений по порядку.Даже если они являются последовательными, они могут выполняться одновременно на одном ядре ЦП, что приводит к неопределенному порядку выполнения.
На современных чипах x86 несколько инструкций могут выполняться параллельно даже на одном ядре, а в первых процессорах Pentium, выпущенных в 1993 году, x86 могла выполнять несколько инструкций одновременно на одном ядре. Начиная с Pentium Pro в 1995 году, чипы x86 начали запускать наш код не по порядку.
Другими словами, компилятор или ядро ЦП могут изменить порядок рабочих инструкций.
Окончательный порядок выполнения может быть таким
1reg0 = calloc(sizeof(Singleton));2instance = reg0;3reg0.<init>();
Таким образом, другой поток может получить конструктор, который еще не был выполнен.<init>()
пустой объект.
Чтобы избежать этой неожиданной ситуации, нам нужно добавить volatile перед переменной экземпляра:
private static volatile Singleton instance;
После добавления выполняем его снова, и видим, что в сгенерированном ассемблерном коде есть инструкция, содержащая префикс блокировки:
1 0x0000000113fc76a4: movabs $0x7957d2e18,%rax ; {oop(a 'java/lang/Class' = 'com/itzhai/jvm/executeengine/concurrency/Singleton')} 2 0x0000000113fc76ae: mov 0x20(%rsp),%rsi 3 0x0000000113fc76b3: mov %rsi,%r10 4 0x0000000113fc76b6: shr $0x3,%r10 5 0x0000000113fc76ba: mov %r10d,0x68(%rax) 6 0x0000000113fc76be: shr $0x9,%rax 7 0x0000000113fc76c2: movabs $0x10d94f000,%rsi 8 0x0000000113fc76cc: movb $0x0,(%rax,%rsi,1) 9 0x0000000113fc76d0: lock addl $0x0,(%rsp) ;*putstatic instance10 ; - com.itzhai.jvm.executeengine.concurrency.Singleton::getInstance@24 (line 37)
Инструкция, предшествующая инструкции префикса блокировки, является присваиванием экземпляру.
**Барьер памяти:** Эта операция блокировки эквивалентна барьеру памяти. После обнаружения этого префикса блокировки кэш этого процессора будет записан в память, а кэш других процессоров будет признан недействительным, таким образом реализуя переменную可见性
.
В то же время этот барьер памяти может достичь有序性
: Расположение оператора присваивания volatile переменной эквивалентно барьеру памяти, и инструкции до и после оператора присваивания не могут пересечь этот барьер.
Volatile на самом деле обеспечивает видимость и упорядоченность через барьеры памяти.
2.2.2, когда использовать volatile
2.2.2.1 Если вы хотите читать и записывать длинные и двойные переменные, вы можете использовать volatile
long и double являются 64-битными типами данных, и их атомарность зависит от платформы.Многие переменные long и double платформы записываются в два этапа, по 32 бита на шаг, что может привести к несогласованности данных. Вы можете избежать таких проблем, украсив длинные и двойные переменные volatile в Java.
2.2.2.2 Сценарии, в которых необходимо использовать видимость
После того, как поток обновляет конкретное значение (модификация этого значения не зависит от исходного значения и не требует участия в инвариантном ограничении с другими переменными состояния), другие потоки должны иметь возможность видеть его немедленно.
2.2.2.3, для многопоточного доступа необходимо использовать четкие переменные
Изменчивые переменные можно использовать для информирования компилятора о том, что к определенному полю будет обращаться несколько потоков, что не позволит компилятору выполнить какое-либо переупорядочение или какую-либо оптимизацию, особенно в многопоточной среде, что нежелательно.
Следующий пример
1private boolean isActive = thread;2public void printMessage(){3 while(isActive){4 System.out.println("Thread is Active");5 }6}
если нет
, нет гарантии, что один поток увидит обновленное значение isActive из другого потока. Компилятор также может кэшировать значение isActive без необходимости считывать его из основной памяти на каждой итерации. Этих проблем можно избежать, установив isActive в volatile переменную.
2.2.2.4 Проверка двойной блокировки
Примеры классов были перечислены выше.Чтобы обеспечить упорядоченное выполнение инструкций, всем необходимо добавить ключевое слово volatile.
2.2.3, изменчивые точки использования ключевых слов
- только для переменных;
- Убедитесь, что значения переменных всегда считываются из основной памяти, а не из локального кеша Thread, который является рабочей памятью;
- Операции, объявленные с ключевым словом volatile, не обязательно являются атомарными, в зависимости от скомпилированных ассемблерных инструкций;
- За исключением типов long и double, даже если ключевое слово volatile не используется, чтение и запись переменных примитивного типа имеют видимость;
- Если переменная не является общей для нескольких потоков, нет необходимости использовать ключевое слово volatile для этой переменной;
- Доступ к изменчивой переменной никогда не будет иметь шанса на блокировку, потому что мы делаем только простые операции чтения и записи, никакие блокировки не удерживаются и не ожидаются.
2.3 Синхронная работа
Используя синхронизацию, можно достичь единицы атомарности для произвольно больших блоков операторов, что позволяет нам решать проблемы, с которыми не может справиться volatile.read-modify-write
проблема.
2.3.1 Как реализован нижний слой?
Мы можем написать код, чтобы увидеть его код дизассемблирования:
Можно обнаружить, что синхронизированный блок в конечном итоге становитсяmonitorenter
а такжеmonitorexit
Обернутый блок дизассемблированных операторов инструкций.
Ознакомьтесь со спецификацией jvm, чтобы узнать, что делают эти две инструкции Глава 6. Набор инструкций виртуальной машины Java[2]:
**monitorenter:** объект операции является ссылочным объектом, и каждый объект связан с монитором. Если другой поток получит монитор этого объекта, текущий поток будет ждать. Монитор каждого объекта имеет объект-счетчик входов в объектную ссылку.После успешного входа в монитор объектная ссылка монитора + 1, после чего поток становится владельцем монитора.
Повторное выполнение monitorenter одним и тем же потоком снова войдет в монитор и objectref+1.
**monitorexit:** объект операции является ссылочным объектом, выполняйте эту инструкцию, objectref-1, до тех пор, пока objectref=0, поток не выйдет из монитора и больше не будет владельцем объекта.
2.3.2. Как Synchronized обеспечивает видимость
Synchronized гарантирует, что запись в память потоком до или во время синхронизированного блока становится видимой для других потоков, контролирующих тот же объект в рабочем режиме.
- казнен
monitorenter
После этого монитор освобождается, а рабочая память сбрасывается в основную память, чтобы записи, сделанные этим потоком, были видны другим потокам; - Прежде чем войти в синхронизированный блок, мы должны сначала выполнить
monitorenter
, который делает недействительной рабочую память текущего потока, чтобы переменные можно было перезагрузить из основной памяти.
Что плохого в том, чтобы подумать о следующем коде?
1synchronized (new Object()) {}
2.4, финал
При использовании конечных полей в Java JVM гарантирует, что пользователь объекта увидит окончательное значение поля только после завершения построения.
Для этого JVM вводит конечный конструктор объекта в конце冻结
операция, которая предотвращает любые последующие операции над конструктором или изменение порядка инструкций.
Например:
1instance = new Singleton();
С точки зрения макроса можно считать, что new разбивается на 3 оператора:
1reg0 = calloc(sizeof(Singleton));2reg0.<init>();3instance = reg0;
Перед назначением экземпляра убедитесь,<init>()
При ограничениях конструктора гарантируется, что экземпляр получит окончательное значение.
2.4 О неатомарных двойных и длинных переменных
Выбор реализации виртуальной машины может не гарантировать атомарность операций загрузки, хранения, чтения и записи 64-битных типов данных.
Однако общая реализация виртуальной машины рассматривает операции чтения и записи 64-битных данных почти как атомарные операции, что удобно для кодирования.
2.5, проблема старой версии модели памяти Java
С 1997 года в модели памяти Java, определенной в Спецификации языка Java, были обнаружены серьезные недостатки, которые путают поведение (например, конечное поле изменит свое значение) и лишает компилятор возможности выполнять регулярные оптимизации. С этой целью было представлено предложение JSR 133.[3], JSR 133 определяет новую модель памяти для языка Java, оптимизируя семантику final и volatile, что устраняет недостатки более ранних моделей памяти. До сих пор приведенный выше контент в этой статье основывался на спецификации JSR 133.
старая модельРазрешить переупорядочивание энергозависимых записей с энергонезависимыми операциями чтения и записи., что несовместимо с тем, что большинство разработчиков напрямую думают о volatile, что вызывает путаницу. Программисты часто ошибаются относительно того, что может произойти с их программами из-за неправильной синхронизации их программ, и одна из целей JSR 133 — привлечь внимание к этому факту.
3. Три проблемы, на которые следует обратить внимание при разработке модели памяти Java
3.1 Атомарность
Как гарантировать атомарность?
- В Java мы можем думать, что доступ для чтения и записи основных типов данных (кроме double и long) является атомарным;
- Более широкий спектр атомарных гарантий может быть достигнут с помощью ключевого слова synchronized, и используется нижний уровень.
monitorenter
а такжеmonitorexit
Соответствующие операции: блокировка и разблокировка.
3.2 Видимость
Модель памяти Java обеспечивает видимость за счет синхронизации нового значения с памятью после изменения переменной и обновления значения переменной из основной памяти до того, как переменная будет прочитана, что зависит от основной памяти в качестве среды передачи.
Таким образом реализованы как обычные переменные, так и изменчивые переменные.
Изменчивые переменные могут быть немедленно синхронизированы с основной памятью и сброшены из основной памяти непосредственно перед каждым использованием. Таким образом, volatile гарантирует видимость переменных для многопоточных операций, а обычные переменные — нет.
Два других ключевых слова, которые обеспечивают видимость:
- synchronzied: перед выполнением операции разблокировки переменной переменная должна быть синхронизирована с основной памятью (выполнить операцию сохранения, записи).
- final: после инициализации конечного поля в конструкторе, и конструктор не передает эту ссылку, другие потоки могут видеть значение конечного поля.
3.3 Порядок
При наблюдении внутри потока все операции идут по порядку (семантическая сериализация), но при наблюдении внутри одного потока другого потока все операции происходят не по порядку (вызванные изменением порядка инструкций).
Для достижения упорядоченности:
- ключевое слово volatile: запретить перестановку инструкций;
- Синхронизированный: он реализован на основе переменной, которая позволяет только одному потоку одновременно выполнять операцию блокировки, что означает, что два синхронизированных блока, удерживающих одну и ту же блокировку, могут выполняться только последовательно.
4. Правило первого вхождения модели памяти
Семантика модели памяти Java согласуется с некоторым порядком выполнения операций с памятью (чтение переменной, запись переменной, блокировка, разблокировка) и других операций потока (запуск и присоединение):
-
Правило порядка выполнения программы: в том же потоке операция, написанная впереди, выполняется раньше, чем операция, написанная сзади;
-
Следите за правилами блокировки: операция разблокировки выполняется первой в последующей операции блокировки того же замка;
-
Правило изменчивой переменной: операция записи изменчивой переменной происходит первой перед последующей операцией чтения переменной;
-
Правила запуска потока: метод start() потока выполняется первым для каждого действия этого потока;
-
Правила завершения потока: все операции потока происходят первыми при обнаружении завершения этого потока;
-
Правила финализации объекта: объект появляется первым от конструктора до его метода finalize();
-
Транзитивность: если операция A происходит раньше B, операция B происходит раньше C, то операция A происходит раньше C;
По приведенным выше правилам можно сказать, является ли программа потокобезопасной.
5. Вывод
Что ж, эта статья представлена здесь, я полагаю, что вы уже достаточно глубоко понимаете ответы на приведенные выше вопросы. В следующий раз, когда кто-то будет обсуждать с вами модель памяти Java, задайте ему эти вопросы.
Эта статья написана художественным мышлением на основе соответствующих технических материалов и официальных документов, чтобы обеспечить точность содержания.Если вы обнаружите какие-либо ошибки или упущения, пожалуйста, поднимите руки, чтобы помочь их исправить, я очень благодарен.
Вы можете следить за моим блогом: itzhai.com для получения дополнительных статей, я буду продолжать обновлять технологии, связанные с серверной частью, включая JVM, основу Java, проектирование архитектуры, сетевое программирование, структуры данных, базы данных, алгоритмы, параллельное программирование, распределенные системы и т. д. , связанная информация.
Если вы чувствуете, что получили что-то от чтения этой статьи, вы можете подписаться на мою учетную запись или поставить большой палец вверх или что-то в этом роде. Подпишитесь на мой официальный аккаунт, чтобы получать свежие статьи вовремя.
References
«Глубокое понимание виртуальной машины Java — расширенные функции и лучшие практики JVM»
x86 and amd64 instruction reference
Java Language Specification#17.4. Memory Model
JSR 133 (Java Memory Model) FAQ
How Volatile in Java works? Example of volatile keyword in Java
JSR 133: Java Memory Model and Thread Specification Revision
Эта статьяarthinking
Он написан на основе соответствующей технической информации и официальных документов для обеспечения точности содержания.Если вы обнаружите какие-либо ошибки или упущения, пожалуйста, поднимите руки, чтобы помочь их исправить, я очень благодарен.
Вы можете следить за моим блогом:itzhai.com
Для других статей я буду продолжать обновлять технологии, связанные с серверной частью, включая JVM, основу Java, проектирование архитектуры, сетевое программирование, структуры данных, базы данных, алгоритмы, параллельное программирование, распределенные системы и другой связанный контент.
Если вы чувствуете, что прочитали эту статью, вы можете关注
мой аккаунт или点个赞
, ваша поддержка является движущей силой для моего письма! Подпишитесь на мой официальный аккаунт, чтобы получать свежие статьи вовремя.
Автор этой статьи: артмышление
Ссылка на блог:woohoo.ithome.com/stoozer/how-he и…
Если кто-то лишает вас модели памяти Java, бросьте эти проблемы ему.
Заявление об авторских правах: Авторские права принадлежат автору и не могут быть воспроизведены без разрешения Нарушение должно быть расследовано! Чтобы связаться с автором, пожалуйста, добавьте публичный аккаунт.