Интервьюер спросил меня, что такое JMM

Java
Интервьюер спросил меня, что такое JMM

карта разума

Статья была выбрана Github, добро пожаловать в Star:GitHub.com/Yehongqin/Лай…

Интервьюер: Расскажите мне, что такое JMM

Я не буду спать, если ты исправишь это.

JMM — это модель памяти Java (модель памяти Java). Поскольку существуют определенные различия в доступе к памяти у разных производителей оборудования и разных операционных систем, при выполнении одного и того же кода в разных системах будут возникать различные проблемы. такМодель памяти Java (JMM) скрывает различия в доступе к памяти различного оборудования и операционных систем, так что Java-программы могут достигать согласованных эффектов параллелизма на различных платформах.

Правила модели памяти JavaВсе переменные хранятся в основной памяти, включая переменные экземпляра, статические переменные, но не локальные переменные и параметры метода. Каждый поток имеет свою рабочую память,Поток рабочей памяти удерживает поток, используемую в основной переменной памяти, и копию копирования, операции потоков выполняются на переменных в рабочей памяти.Потоки не могут напрямую читать и записывать переменные в основную память.

Различные потоки также не могут получить доступ к переменным в рабочей памяти друг друга. Передачу значений переменных между потоками нужно делать через основную память.

Если это звучит абстрактно, я могу нарисовать для вас картинку, она будет более интуитивной:

Рабочая память каждого потока независима, и данные операций потока могут выполняться только в рабочей памяти, а затем сбрасываться обратно в основную память. Это основная работа потоков, определенная моделью памяти Java.

Теплое напоминание: некоторые люди неправильно понимают модель памяти Java какСтруктура памяти Java, а затем ответить на кучу, стек, сборку мусора GC, и, наконец, это далеко не тот вопрос, который хочет задать интервьюер.Фактически, общий вопрос о модели памяти Java состоит в том, чтобы задать вопросы о многопоточности и параллелизме Java..

Интервьюер: Что определяет JMM?

Эта простая полноценная модель памяти Java построена вокруг трех характеристик. К ним относятся: атомарность, наглядность, упорядоченность. Можно сказать, что эти три характеристики лежат в основе всего параллелизма Java.

атомарность

Атомарность означает, что операция неделима, непрерывна, и один поток выполняется без вмешательства других потоков.

Интервьюер взял ручку и написал код: могут ли следующие строки кода гарантировать атомарность??

int i = 2;
int j = i;
i++;
i = i + 1;

Первое предложение — это базовая операция присваивания типа, которая должна быть атомарной операцией.

Второе предложение сначала считывает значение i, а затем присваивает его j. Двухшаговая операция не может гарантировать атомарность.

Третье и четвертое предложения на самом деле эквивалентны.Сначала прочитайте значение i, затем +1 и, наконец, присвойте его i.Трехэтапная операция не может гарантировать атомарность.

JMM может гарантировать только базовую атомарность.Если вы хотите гарантировать атомарность блока кода, предоставляются две инструкции байт-кода, monitorenter и monitorrexit, то есть синхронизированное ключевое слово. Таким образом, операции между синхронизированными блоками являются атомарными.

видимость

Видимость означает, что когда один поток изменяет значение общей переменной, другие потоки могут сразу узнать, что оно было изменено. Java использует ключевое слово volatile для обеспечения видимости. Когда переменная изменяется с помощью volatile, переменная будет сброшена в основную память сразу после изменения.Когда другим потокам нужно прочитать переменную, они перейдут в основную память, чтобы прочитать новое значение. Обычные переменные этого не гарантируют.

В дополнение к ключевому слову volatile, final и synchronized также могут обеспечить видимость.

Принцип синхронизирования заключается в том, что разделяемая переменная должна быть синхронизирована с основной памятью после выполнения и до входа в разблокировку.

Поле final-modified после инициализации становится видимым для других потоков, если ни один объект не исчезает (это означает, что объект может использоваться другими потоками после завершения инициализации).

упорядоченность

В Java синхронизированный или изменчивый можно использовать для обеспечения упорядочения операций между несколькими потоками. Есть некоторые отличия в принципе реализации:

Ключевое слово volatile использует барьеры памяти, чтобы запретить переупорядочивание инструкций для обеспечения упорядоченности.

Принцип синхронизации заключается в том, что после того, как поток заблокирован, он должен быть разблокирован, прежде чем другие потоки смогут повторно заблокировать его, чтобы блоки кода, обернутые синхронизацией, выполнялись последовательно между несколькими потоками.

Интервьюер: Расскажите мне о восьми взаимодействиях с памятью.

Хорошо, интервьюер, есть 8 видов операций взаимодействия с памятью, позвольте мне нарисовать для вас картинку:

  • замок (замок), действующий наОсновная памятьПеременная в переменной идентифицируется как эксклюзивное состояние.
  • читать (читать), воздействовать наосновная памятьЗначение переменной передается из основной памяти в рабочую память потока для использования в следующей операции загрузки.
  • нагрузка (нагрузка), действующая нарабочая памятьПеременная операции чтения помещается в копию переменной рабочей памяти.
  • использование (использование), действующее нарабочая памятьПеременная в рабочей памяти передается механизму выполнения, который будет выполняться каждый раз, когда виртуальная машина встречает инструкцию байт-кода, которой необходимо использовать значение переменной.
  • назначать (назначение), воздействовать нарабочая памятьПеременная, которая присваивает значение, полученное от механизма выполнения, копии переменной в рабочей памяти, которую виртуальная машина выполняет всякий раз, когда встречает инструкцию байт-кода, присваивающую значение переменной.
  • магазин (хранилище), действующий нарабочая памятьПеременная, которая передает значение из переменной в рабочей памяти в основную память для использования при последующих операциях записи.
  • писать (писать): действует наосновная памятьОн помещает значение переменной, полученное операцией сохранения из рабочей памяти, в переменную в основной памяти.
  • разблокировать (разблокировать): действует наосновная памятьОн освобождает переменную, находящуюся в заблокированном состоянии, и освобожденная переменная может быть заблокирована другими потоками.

Позвольте мне добавить правила JMM для 8 видов операций взаимодействия с памятью:

  • Одна из операций чтения, загрузки, сохранения и записи не может появляться одна, то есть операция чтения должна быть загружена, а запись должна быть записана после операции сохранения.
  • Потоку не разрешается отбрасывать свою последнюю операцию присваивания, то есть после того, как переменные данные в рабочей памяти изменились, необходимо сообщить об этом в основную память.
  • Потокам не разрешено синхронизировать неназначенные данные из рабочей памяти в основную память.
  • Новая переменная должна быть создана в основной памяти, а рабочей памяти не разрешается напрямую использовать неинициализированные переменные. То есть, прежде чем реализовать операции использования и сохранения над переменной, она должна пройти через операции загрузки и назначения.
  • Переменная может быть заблокирована только одним потоком за раз. После нескольких блокировок для разблокировки необходимо выполнить такое же количество разблокировок.
  • Если операция блокировки выполняется над переменной, все значения этой переменной в рабочей памяти будут очищены. Прежде чем механизм выполнения сможет использовать эту переменную, значение переменной должно быть инициализировано с помощью операции перезагрузки или назначения.
  • Если переменная не заблокирована, вы не можете выполнять операции UNLOCK. Вы не можете разблокировать переменную, которая заблокирована другими потоками.
  • Прежде чем поток разблокирует переменную, он должен сначала синхронизировать переменную обратно в основную память.

Интервьюер: Расскажите мне о ключевом слове volatile.

Сердце: Это может быть главным событием, но оно не может пойти не так~

Во многих параллельных программах используется ключевое слово volatile.Основные функции включают в себя два момента:

  1. Гарантирует видимость переменных между потоками.
  2. Отключает переупорядочивание команд процессором.

видимость

Переменная, модифицированная volatile, когда поток изменяет значение переменной, сразу становятся видны другие потоки. Обычные переменные необходимо перечитать, чтобы получить последнее значение.

Процесс volatile, гарантирующий видимость, вероятно, это один процесс:

Гарантирует ли volatile безопасность потоков?

Давайте сначала поговорим о выводе: volatile не может гарантировать потокобезопасность.

Как это доказать, мы можем увидеть результат выполнения следующего фрагмента кода:

/**
 * @author Ye Hongzhi 公众号:java技术爱好者
 **/
public class VolatileTest extends Thread {

    private static volatile int count = 0;

    public static void main(String[] args) throws Exception {
        Vector<Thread> threads = new Vector<>();
        for (int i = 0; i < 100; i++) {
            VolatileTest thread = new VolatileTest();
            threads.add(thread);
            thread.start();
        }
        //等待子线程全部完成
        for (Thread thread : threads) {
            thread.join();
        }
        //输出结果,正确结果应该是1000,实际却是984
        System.out.println(count);//984
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                //休眠500毫秒
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
            count++;
        }
    }
}

Почему volatile не гарантирует потокобезопасность?

Очень просто ах, не может гарантировать видимость атомарных операций, сказал, прежде чем счет ++ не является атомарными операциями, так как будет три шага, сначала прочитать значение счетчика, затем +1, и, наконец, обратное назначение переменной счетчика. Необходимость обеспечения потокобезопасности требует использования синхронизированного ключевого слова lock или lock, заблокированного для подсчета кода ++:

private static synchronized void add() {
    count++;
}

Отключить переупорядочивание инструкций

Прежде всего, давайте поговорим о семантике as-if-semantic.Как бы ни был переупорядочен результат выполнения (однопоточной) программы, он не может быть изменен.

Чтобы инструкции больше соответствовали характеристикам выполнения ЦП, максимизировали производительность машины и повысили эффективность выполнения программы, если конечный результат программы равен результату ее сериализации. , порядок выполнения инструкций может не соответствовать логическому порядку кода.Этот процесс называетсяизменение порядка инструкций.

Существует три типа переупорядочения: переупорядочение компилятора, параллельное переупорядочение на уровне инструкций и переупорядочение системы памяти. Весь процесс выглядит следующим образом:

Переупорядочение инструкций не проблема в одном потоке, не влияет на результат выполнения, а также повышает производительность. Однако в многопоточной среде нет никакой гарантии, что результат выполнения не будет затронут.

Так что в многопоточной среде необходимо запретить переупорядочивание инструкций.

Ключевое слово volatile, запрещающее переупорядочивание инструкций, имеет два значения:

  • Когда программа выполняет операцию чтения или запись в изменчивую переменную, все изменения в предыдущих операциях должны быть выполнены, и результаты должны быть видны последующим операциям, а последующие операции не должны выполняться. .

  • При выполнении оптимизации инструкций оператор, который обращается к volatile-переменной, не может выполняться после него, а оператор после volatile-переменной не может выполняться перед ним.

Вот пример:

private static int a;//非volatile修饰变量
private static int b;//非volatile修饰变量
private static volatile int k;//volatile修饰变量

private void hello() {
    a = 1;  //语句1
    b = 2;  //语句2
    k = 3;  //语句3
    a = 4;  //语句4
    b = 5;  //语句5
    //以下省略...
}

Переменные A, B - неретильные переменные, K модифицирован с использованием летучих. Следовательно, утверждение 3 заявления 3 не может быть размещено до 1,2, 4,5 нельзя разместить после оператора. 1 и 2, но последовательность операторов не гарантируется. Аналогично, 4,5 заявление не может гарантировать порядок.

И выполнить 3 оператору, когда оператор конечно закончен 1, 2 и 1 и 2 для результатов выполнения оператора видны операторы 3,4,5.

В чем заключается принцип volatile, запрещающий переупорядочивание инструкций

Прежде всего, поговорим о барьерах памяти.Барьеры памяти можно разделить на следующие категории:

  • Барьер LoadLoad: Для таких операторов Load1, LoadLoad, Load2. Перед доступом к данным, которые должны быть прочитаны Load2 и последующими операциями чтения, убедитесь, что данные, которые должны быть прочитаны Load1, были прочитаны.

  • Барьер StoreStore: для таких операторов Store1, StoreStore, Store2 до выполнения операций Store2 и последующих операций записи операции записи Store1 гарантированно будут видны другим процессорам.

  • Барьер LoadStore: для таких операторов Load1, LoadStore, Store2 данные, которые должны быть прочитаны Load1, гарантированно будут прочитаны до того, как Store2 и последующие операции записи будут сброшены.

  • Барьер StoreLoad: для таких операторов Store1, StoreLoad, Load2 и Load2 перед выполнением всех последующих операций чтения, чтобы обеспечить видимость записи Store1 для всех процессоров.

Барьеры LoadLoad вставляются после каждой операции чтения энергозависимой памяти, а барьеры LoadStore вставляются после операций чтения.

Перед каждой операцией энергозависимой записи вставляется барьер StoreStore, за которым следует барьер SotreLoad.

Общий принцип таков.

Интервьюер: Неплохо, в основном, это прикрыто, еще не слишком рано, сегодняшнее интервью окончено, возвращайтесь и ждите уведомления~

Суммировать

Чтобы изучить параллельное программирование, модель памяти Java является первой остановкой. Три характеристики: атомарность, упорядоченность и наглядность почти пронизывают параллельное программирование, которое можно охарактеризовать как базовые знания. Это играет роль в закладывании основы для более глубокого изучения позже.

В этой статье основное внимание уделяется тому, как работает модель памяти Java (JMM), трем основным характеристикам и ключевому слову volatile. Почему вам нравится спрашивать о ключевом слове volatile, потому чтоКлючевое слово volatile может вытащить много вещей, таких как видимость, порядок, барьеры памяти и т. д.. Вы можете сразу увидеть технический уровень интервьюируемого, ведь интервьюер также хочет эффективно отсеивать таланты, соответствующие требованиям.

Код для всех приведенных выше примеров был загружен на Github:

GitHub.com/yehongqin/no…

Ставьте лайки, если считаете это полезным, ваши лайки — самая большая мотивация для моего творчества.~

Не хочу быть соленой рыбой, я программист, стремящийся запомниться всем. Увидимся в следующий раз! ! !

Возможности ограничены, если есть какие-то ошибки или неуместности, пожалуйста, критикуйте и исправьте их, учитесь и общайтесь вместе!