Многие онлайн -материалы описывают модель памяти Java, которая представит, что существует основная память, и в каждом рабочем потоке есть своя рабочая память. В основной памяти будет одна часть данных и одна часть в рабочей памяти. Между рабочей памятью и основной памятью будут различные атомные операции.
Следующая картина из этого блога
Однако из -за непрерывной эволюции версии Java модель памяти также изменилась. В этой статье рассказывается только о некоторых особенностях модели памяти Java. Будь то новая модель памяти или старая модель памяти, она будет выглядеть более яснее после понимания этих функций.
1. Атомность
Атомность означает, что операция непрерывна. Даже когда несколько потоков выполняются вместе, после начала операции она не будет нарушена другими потоками.
Обычно считается, что инструкции процессора являются атомными операциями, но код, который мы пишем, не обязательно является атомным операциями.
Например, i ++. Эта операция не является атомной операцией, она в основном разделена на 3 операции, прочитайте I, выполните +1 и назначает значение i.
Предположим, есть две нити. Когда первый поток читает I = 1, операция +1 еще не выполнена, и переключитесь на второй поток. В настоящее время второй поток также читается I = 1. Затем два потока выполняют последующие операции +1, а затем присваивают значения обратно, я не 3, но 2. Очевидно, что в данных существует несоответствие.
Например, чтение 64-разрядного значения на 32-битной JVM не является атомной операцией. Конечно, 32-битный JVM читает 32-разрядные целые числа как атомная операция.
2. Заказ
Во время параллелистика выполнение программы может быть вне порядка.
Когда компьютер выполняет код, он не обязательно выполняется в порядке программы.
класс orderexample {int a = 0; логический флаг = false; public void writer () {a = 1; flag = true; } public void reader () {if (flag) {int i = a +1; }}} Например, в приведенном выше коде два метода называются двумя потоками соответственно. Согласно здравому смыслу, письменный поток должен сначала выполнить a = 1, а затем выполнить flag = true. Когда чтение поток читается, i = 2;
Но поскольку a = 1 и flag = true, нет логической корреляции. Следовательно, можно отменить порядок выполнения, и сначала можно выполнить flag = true, а затем a = 1. В настоящее время, когда Flag = true, переключитесь на поток чтения. В настоящее время a = 1 еще не выполнено, тогда поток чтения будет i = 1.
Конечно, это не абсолютно. Вполне возможно, что будет не в порядке и может не произойти.
Так почему же не в порядке? Это начинается с инструкции процессора. После составления кода в Java он, наконец, преобразуется в код сборки.
Выполнение инструкции можно разделить на многие шаги. Предполагая, что инструкция процессора разделена на следующие шаги
Предположим, здесь есть две инструкции
Вообще говоря, мы будем думать, что инструкции выполняются последовательно, сначала выполните инструкцию 1, а затем выполнить инструкцию 2. При условии, что каждый шаг требует 1 периода времени ЦП, а затем выполнение этих двух инструкций требует 10 периодов времени ЦП, что слишком неэффективно для этого. На самом деле, инструкции выполняются параллельно. Конечно, когда первая инструкция выполняется, если вторая инструкция не может выполнять, если, поскольку инструкция регистрируется и тому подобное, не может быть занята одновременно. Таким образом, как показано на рисунке выше, две инструкции выполняются параллельно относительно ошеломленным образом. Когда инструкция 1 выполняет идентификатор, инструкция 2 выполняет IF. Таким образом, две инструкции были выполнены только в 6 периодах процессора, что было относительно эффективным.
Согласно этой идее, давайте посмотрим, как выполняется инструкция A = B+C.
Как показано на рисунке, во время операции добавления (x) выполняется операция на холостом ходу (x), потому что, когда вы хотите добавить B и C, когда на рисунке Ad Add, C не считывается из памяти (C только с чтения из памяти, когда операция MEM завершена. Здесь есть вопрос. В это время нет никакого записи (WB) в R2, как R1 может быть добавлено? Таким образом, нет необходимости ждать, пока WB выполнит, прежде чем будет выполнено добавление). Следовательно, в операции добавления будет простоя (x). В операции SW, поскольку EX -инструкция не может быть выполнена одновременно с инструкцией Add EX, будет простоя (x) время.
Далее, давайте приведем немного более сложный пример
a = b+c
d = ef
Соответствующие инструкции следующие
Причина похожа на вышесказанное, поэтому я не буду анализировать это здесь. Мы обнаружили, что здесь много X, и есть много временных циклов, и также затронута производительность. Есть ли способ уменьшить количество XS?
Мы надеемся использовать некоторые операции для заполнения свободного времени X, потому что ADD имеет зависимость данных с вышеуказанными инструкциями, и мы надеемся использовать некоторые инструкции без зависимости от данных, чтобы заполнить свободное время, генерируемое зависимостью данных.
Мы изменили порядок инструкций
После изменения порядка инструкций x устранен. Общий период времени выполнения также уменьшился.
Переупорядочение инструкций может сделать трубопровод более плавным
Конечно, принцип перестройки обучения заключается в том, что он не может разрушить семантику серийной программы. Например, a = 1, b = a+1, такие инструкции не будут перестановлены, поскольку последовательный результат перестройки отличается от исходной.
Перестановка инструкций - это просто способ оптимизировать компилятор или процессор, и эта оптимизация вызвала проблемы с программой в начале этой главы.
Как это решить? Используйте нестабильное ключевое слово, будет введена эта последующая серия.
3. видимость
Видимость относится к тому, могут ли другие потоки сразу узнать модификацию, когда поток изменяет значение общей переменной.
Вопросы видимости могут возникнуть в различных ссылках. Например, только что упомянутое изменение инструкций также вызовет проблемы видимости, и, кроме того, оптимизация компилятора или оптимизация определенного оборудования также вызовет проблемы видимости.
Например, поток оптимизирует общее значение в памяти, в то время как другой поток оптимизирует общее значение в кэше. При изменении значения в памяти кэш не знает модификации.
Например, некоторые оптимизации аппаратного обеспечения, когда программа пишет несколько раз по одному и тому же адресу, она будет думать, что она не нужна и только сохраняет последнюю запись, поэтому данные, написанные ранее, будут невидимы в других потоках.
Короче говоря, большинство проблем с видимости связаны с оптимизацией.
Далее, давайте посмотрим на проблему видимости, возникающая на уровне виртуальной машины Java
Проблема возникает из блога
пакет edu.hushi.jvm; /** * * * @author -10 * */public Class VisibbieSt Extends Thread {Private Boolean Stop; public void run () {int i = 0; while (! Stop) {i ++; } System.out.println ("Finish Loop, i =" + i); } public void stopit () {stop = true; } public boolean getStop () {return Stop; } public static void main (string [] args) бросает исключение {visibbeStest v = new VisebibibibleTest (); v.start (); Thread.sleep (1000); v.stopit (); Thread.sleep (2000); System.out.println ("Finish Main"); System.out.println (v.getStop ()); }} Код очень прост. Поток V удерживает I ++ в цикле Whos, пока основной поток не вызовет метод остановки, изменяя значение переменной остановки в потоке V, чтобы остановить цикл.
Проблемы возникают, когда, казалось бы, простые запуска кода. Эта программа может помешать потокам выполнять операции самостоятельной реализации в режиме клиента, но в режиме сервера это будет сначала бесконечная цикл. (Больше оптимизации JVM в режиме сервера)
Большинство 64-битных систем-это режим сервера, и запускаются в режиме сервера:
закончить основное
истинный
Только эти два предложения будут напечатаны, но финишная петля не будет напечатана. Но вы можете обнаружить, что стоимость остановки уже верно.
Автор этого блога использует инструменты для восстановления программы для кода сборки
Здесь перехватывается только часть кода сборки, а красная часть - это петля. Ясно видно, что только 0x0193BF9D является проверкой остановки, в то время как красная часть не принимает стоп -ценность, поэтому выполняется бесконечная петля.
Это результат оптимизации JVM. Как этого избежать? Как переупорядочение директивы, используйте нестабильное ключевое слово.
Если добавлен волатильный, восстановите его в код сборки, и вы обнаружите, что каждый цикл получит значение остановки.
Далее, давайте посмотрим на некоторые примеры в «спецификации языка на Java»
На приведенном выше рисунке показано, что переупорядочение инструкции приведет к различным результатам.
Причина, по которой R5 = R2 сделан на рисунке выше, заключается в том, что R2 = R1.x, R5 = R1.x, и он напрямую оптимизируется на R5 = R2 во время компиляции. В конце концов, результаты разные.
4.
5. Концепция безопасности потока
Это относится к тому факту, что, когда в многопоточной среде вызывается определенная библиотека функций или функций, она может правильно обрабатывать локальные переменные каждого потока и разрешить правильно выполнять функции программы.
Например, пример i ++, упомянутый в начале
Это приведет к небезопасности потока.
Для получения подробной информации о безопасности потоков, пожалуйста, обратитесь к этому блогу, который я написал ранее, или следуйте последующей серии, и вы также будете говорить о связанном контенте.