[Java] Несколько распространенных вопросов на осеннем собеседовании

интервью Java HTTPS HTTP

предисловие

Только лысина может стать сильнее

Redis все еще смотрит на него.Сегодня я поделюсь некоторыми вопросами интервью (относительно распространенными), которые я видел (встречал) в Qiu Zhao.

0. конечное ключевое слово

Кратко поговорим о ключевом слове final, что можно использовать для модификации?

Я столкнулся с этим вопросом в реальном интервью, и я не очень хорошо ответил на него в то время, так что давайте разберемся сейчас.

final может изменять классы, методы, переменные-члены

  • Когда конечный класс изменяется, это означает, что класс не может быть унаследован
  • Когда метод окончательно изменен, это означает, что метод не может быть переопределен.
    • На ранних стадиях, может использовать final модифицированные методы, а все вызовы, сделанные компилятором к этим методам, преобразуются во встроенные вызовы, что повышает эффективность (но сейчас нас это вообще не волнует, компилятор и JVM все умнее и умнее)
  • Когда final изменяет переменные-члены, возможны два случая:
    • Если модификация является базовым типом, это означает, что значение, представленное этой переменной, никогда не может быть изменено (не может быть переназначено)!
    • Если модификация является ссылочным типом, ссылка переменной не может быть изменена, но содержимое объекта, представленного ссылкой, является переменным!

Стоит отметить, что:Переменная-член, измененная final, не обязательно является константой времени компиляции.. Например, мы можем написать такой код:private final int java3y = new Randon().nextInt(20);

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

Это может произойти при написании анонимных внутренних классов.Переменные, которые могут использоваться анонимными внутренними классами:

  • Переменная экземпляра внешнего класса
  • локальные переменные внутри метода или области видимости
  • параметры метода

class Outer {


    // string:外部类的实例变量
    String string = "";


    //ch:方法的参数
    void outerTest(final char ch) {

        // integer:方法内局部变量
        final Integer integer = 1;
        new Inner() {
            void innerTest() {
                System.out.println(string);
                System.out.println(ch);
                System.out.println(integer);
            }
        };

    }
    public static void main(String[] args) {
        new Outer().outerTest(' ');
    }
    class Inner {
    }
}

Среди них мы видим: локальные переменные и параметры метода в методе или области должны бытьЯвно используйте ключевое слово final для украшения(Под jdk1.7)!

Если вы переключитесь на среду компиляции jdk1.8, вы сможете скомпилировать ~

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

  • Java реализует замыкания только в форме захвата по значению, то есть анонимные функции будутсделать новую копию свободной переменной, то вне функции и внутри функции естьдваданные.
  • Для достижения цели согласованности внутренних и внешних данных, толькоДве переменные остаются неизменными. До JDK8 требовалась финальная модификация, JDK8 умнее и может эффективно использовать окончательный метод.

Зачем ограничивать final только для параметров в методах, в то время как доступ к свойствам внешних классов бесплатный

Внутренний класс содержитссылка на экземпляр внешнего класса, внутренний класс получает доступ к переменным-членам внешнего класса через эту ссылку.

  • Эти ссылочные данные изменяются во внутреннем классе, внешний классСноваДанные, полученные на момент приобретения, согласуются!

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

Использованная литература:

  • Почему ссылка на параметр анонимного внутреннего класса является окончательной?Ууху. Call.com/question/21…

Во-первых, разница между char и varchar

  1. char имеет фиксированную длину, varchar имеет переменную длину. варчар:Если исходное место хранения не может удовлетворить его потребности в хранении, требуются некоторые дополнительные операции, в зависимости от механизма хранения, некоторые будут использоватьразъемный механизм, некоторое использованиепейджинговый механизм.
  2. char и varchar хранятся в байтахопределенный набор символоврешить (ранее написано с ошибкой);
  3. char имеет фиксированную длину, если длины недостаточно, он заменяется пробелом. varchar представляет фактическую длину типа данных

Соображения по выбору:

  • Если длина поля большекороткаяи длина между символамиодинаковой или даже одинаковой длины, будет использовать тип символа char

Во-вторых, проблема последовательной печати нескольких потоков.

Три потока печатают соответственно A, B и C. Эти три потока должны выполняться вместе, печатать n раз и выводить строку типа «ABCABCABC...».

Оригинальный блогер дал 4 способа, я думаю, что этот способ семафора относительно прост и понятен, вот явставитьВзгляните (подробности вы можете узнать у оригинального блогера).


public class PrintABCUsingSemaphore {
    private int times;
    private Semaphore semaphoreA = new Semaphore(1);
    private Semaphore semaphoreB = new Semaphore(0);
    private Semaphore semaphoreC = new Semaphore(0);

    public PrintABCUsingSemaphore(int times) {
        this.times = times;
    }

    public static void main(String[] args) {
        PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(10);

        // 非静态方法引用  x::toString   和() -> x.toString() 是等价的!
        new Thread(printABC::printA).start();
        new Thread(printABC::printB).start();
        new Thread(printABC::printC).start();

        /*new Thread(() -> printABC.printA()).start();
        new Thread(() -> printABC.printB()).start();
        new Thread(() -> printABC.printC()).start();
*/
    }

    public void printA() {
        try {
            print("A", semaphoreA, semaphoreB);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void printB() {
        try {
            print("B", semaphoreB, semaphoreC);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void printC() {
        try {
            print("C", semaphoreC, semaphoreA);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void print(String name, Semaphore current, Semaphore next)
            throws InterruptedException {
        for (int i = 0; i < times; i++) {
            current.acquire();
            System.out.print(name);
            next.release();
        }
    }
}


14.09.2018 18:15:36 yy Вышли письменные тестовые вопросы..

3. Производители и потребители

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

На самом деле логика несложная, ее можно свести к двум предложениям:

  • Если очередь производителя заполнена (цикл while определяет, заполнена ли она), подождите. Если очередь производителя не заполнена, производственные данные небудитьПотребители потребляют.
  • Если очередь потребителя пуста (цикл while определяет, пуста ли она), подождите. Если очередь потребителя не пуста, используйте данные ибудитьПроизводители производят.

Основываясь на исходном авторском коде, я модифицировал некоторые части и дал соответствующие комментарии (исходник оригинального автора прилагается ниже, а заинтересованные студенты могут изучить исходный текст)

Режиссер:


import java.util.Random;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

public class Producer implements Runnable {

    // true--->生产者一直执行,false--->停掉生产者
    private volatile boolean isRunning = true;

    // 公共资源
    private final Vector sharedQueue;

    // 公共资源的最大数量
    private final int SIZE;

    // 生产数据
    private static AtomicInteger count = new AtomicInteger();

    public Producer(Vector sharedQueue, int SIZE) {
        this.sharedQueue = sharedQueue;
        this.SIZE = SIZE;
    }

    @Override
    public void run() {
        int data;
        Random r = new Random();

        System.out.println("start producer id = " + Thread.currentThread().getId());
        try {
            while (isRunning) {
                // 模拟延迟
                Thread.sleep(r.nextInt(1000));

                // 当队列满时阻塞等待
                while (sharedQueue.size() == SIZE) {
                    synchronized (sharedQueue) {
                        System.out.println("Queue is full, producer " + Thread.currentThread().getId()
                                + " is waiting, size:" + sharedQueue.size());
                        sharedQueue.wait();
                    }
                }

                // 队列不满时持续创造新元素
                synchronized (sharedQueue) {
                    // 生产数据
                    data = count.incrementAndGet();
                    sharedQueue.add(data);

                    System.out.println("producer create data:" + data + ", size:" + sharedQueue.size());
                    sharedQueue.notifyAll();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupted();
        }
    }

    public void stop() {
        isRunning = false;
    }
}

потребитель:


import java.util.Random;
import java.util.Vector;

public class Consumer implements Runnable {

    // 公共资源
    private final Vector sharedQueue;

    public Consumer(Vector sharedQueue) {
        this.sharedQueue = sharedQueue;
    }

    @Override
    public void run() {

        Random r = new Random();

        System.out.println("start consumer id = " + Thread.currentThread().getId());
        try {
            while (true) {
                // 模拟延迟
                Thread.sleep(r.nextInt(1000));

                // 当队列空时阻塞等待
                while (sharedQueue.isEmpty()) {
                    synchronized (sharedQueue) {
                        System.out.println("Queue is empty, consumer " + Thread.currentThread().getId()
                                + " is waiting, size:" + sharedQueue.size());
                        sharedQueue.wait();
                    }
                }
                // 队列不空时持续消费元素
                synchronized (sharedQueue) {
                    System.out.println("consumer consume data:" + sharedQueue.remove(0) + ", size:" + sharedQueue.size());
                    sharedQueue.notifyAll();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
}

Тест основного метода:


import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test2 {


    public static void main(String[] args) throws InterruptedException {

        // 1.构建内存缓冲区
        Vector sharedQueue = new Vector();
        int size = 4;

        // 2.建立线程池和线程
        ExecutorService service = Executors.newCachedThreadPool();
        Producer prodThread1 = new Producer(sharedQueue, size);
        Producer prodThread2 = new Producer(sharedQueue, size);
        Producer prodThread3 = new Producer(sharedQueue, size);
        Consumer consThread1 = new Consumer(sharedQueue);
        Consumer consThread2 = new Consumer(sharedQueue);
        Consumer consThread3 = new Consumer(sharedQueue);
        service.execute(prodThread1);
        service.execute(prodThread2);
        service.execute(prodThread3);
        service.execute(consThread1);
        service.execute(consThread2);
        service.execute(consThread3);

        // 3.睡一会儿然后尝试停止生产者(结束循环)
        Thread.sleep(10 * 1000);
        prodThread1.stop();
        prodThread2.stop();
        prodThread3.stop();

        // 4.再睡一会儿关闭线程池
        Thread.sleep(3000);

        // 5.shutdown()等待任务执行完才中断线程(因为消费者一直在运行的,所以会发现程序无法结束)
        service.shutdown();


    }
}

Кроме того, в приведенном выше исходном тексте также говорилось, что потребители и производители могут быть реализованы с использованием блокирующих очередей. Это не требует, чтобы мы вручную писалиwait/notifyкод, это будет просто. Вы можете обратиться к:

В-четвертых, алгоритм [1]

Теперь мне нужно реализовать стек. Помимо обычных операций push и pop, этот стек также может выполнять операции getMin. После вызова метода getMin он вернет минимальное значение текущего стека. Что бы вы сделали? Вы можете предположить, что стек заполнен целыми числами

решение:

  • Используйте переменную min, чтобы запомнить минимальное значение, и каждый раз, когда вы нажимаете, смотрите, нужно ли обновлять min.
    • Если min выскакивает, при повторном считывании вы можете только пройтись по элементам в стеке, чтобы снова найти минимальное значение.
    • Резюме: временная сложность pop — O(n), push — O(1), а пространственная — O(1).
  • использоватьВспомогательный стекчтобы сохранить минимальное значение. Если текущее значение, которое нужно поместить, меньше, чем минимальное значение вспомогательного стека, значением, помещаемым во вспомогательный стек, является минимальное значение.
    • Резюме: временная сложность push и pop равна O(1), а пространственная — O(n). Типичный пример обмена пространства на время.

import java.util.ArrayList;
import java.util.List;

public class MinStack {

    private List<Integer> data = new ArrayList<Integer>();
    private List<Integer> mins = new ArrayList<Integer>();

    public void push(int num) {
        data.add(num);
        if (mins.size() == 0) {
            // 初始化mins
            mins.add(num);
        } else {
            // 辅助栈mins每次push当时最小值
            int min = getMin();
            if (num >= min) {
                mins.add(min);
            } else {
                mins.add(num);
            }
        }
    }

    public int pop() {
        // 栈空,异常,返回-1
        if (data.size() == 0) {
            return -1;
        }
        // pop时两栈同步pop
        mins.remove(mins.size() - 1);
        return data.remove(data.size() - 1);
    }

    public int getMin() {
        // 栈空,异常,返回-1
        if (mins.size() == 0) {
            return -1;
        }
        // 返回mins栈顶元素
        return mins.get(mins.size() - 1);
    }

}

Продолжайте оптимизировать:

  • Когда стек пуст, возврат -1 может привести к неоднозначности (что, если значение push равно -1?), здесь мы можем использовать Java Exception для оптимизации
  • Пространственная оптимизация алгоритма: В приведенном выше коде мы можем обнаружить, что количество элементов в стеке данных и стеке mins всегда равно, а стек mins хранит почти все самые маленькие значения (эта часть повторяется!)
    • Таким образом, мы можем сделать это: при нажатии, если значение стека min меньше значения стека min, оно будет помещено в стек mins. Точно так же при всплытии, если значение pop равно минимальному значению минут, минуты выскочат из стека, иначе минуты не выскочат из стека!
    • Вышеупомянутый подход может определенно избежать того, чтобы вспомогательный стек mins имел одни и те же элементы!

Однако, если переданное значение является минимальным, то наш вспомогательный стек mins по-прежнему будет содержать большое количество повторяющихся элементов.В настоящее время мы можем использоватьпоказатель(Вспомогательный стек mins хранит минимальный индекс, а не конкретное значение)!

Окончательный код:


import java.util.ArrayList;
import java.util.List;


public class MinStack {

    private List<Integer> data = new ArrayList<Integer>();
    private List<Integer> mins = new ArrayList<Integer>();

    public void push(int num) throws Exception {
        data.add(num);
        if(mins.size() == 0) {
            // 初始化mins
            mins.add(0);
        } else {
            // 辅助栈mins push最小值的索引
            int min = getMin();
            if (num < min) {
                mins.add(data.size() - 1);
            }
        }
    }

    public int pop() throws Exception {
        // 栈空,抛出异常
        if(data.size() == 0) {
            throw new Exception("栈为空");
        }
        // pop时先获取索引
        int popIndex = data.size() - 1;
        // 获取mins栈顶元素,它是最小值索引
        int minIndex = mins.get(mins.size() - 1);
        // 如果pop出去的索引就是最小值索引,mins才出栈
        if(popIndex == minIndex) {
            mins.remove(mins.size() - 1);
        }
        return data.remove(data.size() - 1);
    }

    public int getMin() throws Exception {
        // 栈空,抛出异常
        if(data.size() == 0) {
            throw new Exception("栈为空");
        }
        // 获取mins栈顶元素,它是最小值索引
        int minIndex = mins.get(mins.size() - 1);
        return data.get(minIndex);
    }

}

Использованная литература:

5. HashMap при многопоточности

Как мы все знаем, HashMap не является потокобезопасным классом. Но на собеседовании можно спросить: что будет, если использовать HashMap в многопоточной среде? ?

в заключении:

  • put()Многопоточная несогласованность данных (потеря данных), вызванная
  • resize()операция приводит к круговому связанному списку
    • jdk1.8 решил проблему циклической цепочки (объявить две пары указателей и поддерживать два связанных списка)
  • Механизм отказоустойчивости, одновременное удаление/изменение текущего HashMap вызовет исключение ConcurrentModificationException.

Использованная литература:

Шесть, разница между Spring и Springboot

1. SpringBoot может создавать независимые приложения Spring.

2. Упрощение конфигурации Spring

  • Из-за громоздкости конфигурации Spring когда-то называли «конфигурационным адом» Различные конфигурации XML и Annotation завораживают, и если что-то пойдет не так, то сложно найти причину.
  • Проект Spring Boot призван решить проблему громоздкой конфигурации и максимально реализовать соглашение по конфигурации (соглашение о конфигурации).
    • Предоставляет ряд пакетов зависимостей, чтобы сделать некоторые другие работы из коробки. Он имеет встроенный «начальный POM», который используется для сборки проекта.Высоко упакованный, чтобы максимизировать конфигурацию упрощенных сборок проекта.

3. Встроенный контейнер Tomcat, Jetty, развертывание пакета WAR не требуется.

7. G1 и CMS

Сборщик G1 предназначен для замены сборщика CMS.По сравнению с CMS он лучше работает в следующих аспектах:

  • G1 представляет собойОрганизуйте памятьсборщик мусора процесса,Не вызывает большой фрагментации памяти.
    • CMS использует алгоритм сборки мусора с маркировкой и очисткой, который может привести к сильной фрагментации памяти.
  • Stop The World (STW) G1 более управляем, и G1 добавилмеханизм предсказания, пользователь можетУкажите желаемое время паузы.

Дальнейшее чтение:

Восемь, массивные решения для данных

Обработка массивных данных также является часто проверяемой точкой знаний, которая относительно распространена как в интервью, так и в письменных тестах. Мне посчастливилось прочитать следующие статьи, в которых изложены некоторые идеи по решению массивных данных:

  • Фильтр Блума Фильтр Блума
    • Область применения: его можно использовать для создания словаря данных, выполнения взвешивания данных или установки пересечений.
  • Hashing
    • Сфера применения: базовые структуры данных для быстрого поиска и удаления, обычно требующие всего объема данных, который может поместиться в памяти.
  • bit-map
    • Сфера применения: он может быстро искать, оценивать и удалять данные.Вообще говоря, диапазон данных менее чем в 10 раз больше, чем у int.
  • куча
    • Сфера применения: первые n массивных данных велики, а n относительно малы, кучу можно разместить в памяти
  • Двухслойное деление ковша----по сути это и есть идея "разделяй и властвуй", акцентируя внимание на умении "разделять"!
    • Применимый диапазон: k-й наибольший, средний, уникальный или повторяющийся номер
  • индекс базы данных
    • Область применения: добавление, удаление, изменение и проверка больших объемов данных.
  • Перевернутый индекс
    • Сфера применения: поисковая система, запрос по ключевым словам
  • внешняя сортировка
    • Сфера применения: сортировка больших данных, дедупликация
  • дерево
    • Сфера применения: большой объем данных, много повторений, но в память можно поместить небольшие типы данных
  • Распределенная обработка mapreduce
    • Сфера применения: объем данных большой, но тип данных небольшой и может быть помещен в память.

Подробности смотрите в исходном тексте:

  • Десять вопросов для интервью по массовой обработке данных и краткое изложение десяти методов:blog.CSDN.net/V_July_V/AR…

9. Идемпотентность

9.1 HTTP-идемпотентность

Вчера я написал набор письменных тестовых вопросов в классическом HTTPget/postразница. Я вернулся сегодня и искал, и обнаружил, что это было то же самое, что и предыдущее понимание.немного не в себе.

Если кто-то занимается веб-разработкой в ​​первую очередь, скорее всего,Рассматривайте способ, которым HTML использует протокол HTTP, как единственный разумный способ использования протокола HTTP. тем самым совершая ошибку обобщения

Чисто с точки зрения спецификации протокола HTTP мы могли бы сделать вывод, чтоGET/POSTРазница бесполезна. (Но прочитайте всю статью,на мой взгляд: Если интервьюGET/POSTРазница, лучше отвечать в сценарии веб-разработки по умолчанию, это может быть тот ответ, который хочет интервьюер)

Использованная литература:


Среди них я также изучил концепцию идемпотентности, так что я тоже делаю заметки~~~

Методы также могут иметь свойство «идемпотентности» в том смысле, что (помимо ошибок или проблем с истечением срока действия) побочные эффекты N > 0 идентичных запросов такие же, как и для одного запроса.

По определению идемпотентность метода HTTPУказывает, что один и несколько запросов к ресурсу должны иметь одинаковые побочные эффекты..

  • Вот краткое описание значения «побочных эффектов»: это означает, что когда вы отправляете запрос,Статус ресурса на сайте не изменен, то есть запрос считается свободным от побочных эффектов

HTTPGET/POST/DELETE/PUTКогда метод идемпотентный:

  • GETявляется идемпотентным и не имеет побочных эффектов
    • Например, я хочу получить заказ, идентификатор которого равен 2:http://localhost/order/2,использоватьGETПолучить несколько раз, этот заказ (ресурс) с ID 2 являетсяне изменитсяиз!
  • DELETE/PUTявляется идемпотентным и имеет побочные эффекты
    • Например, я хочу удалить или обновить заказ с идентификатором 2:http://localhost/order/2,использоватьPUT/DELETEнесколько запросов, этот заказ (ресурс) с ID 2только одно изменение(У него есть побочные эффекты)! но продолжайте обновлять запрос несколько раз,Окончательное состояние заказа с идентификатором 2 соответствует
  • POSTнеидемпотентный и имеет побочные эффекты
    • Например, я хочу создать заказ с именем 3y:http://localhost/order,использоватьPOSTНесколько запросов, на этот раз можетСоздайте несколько заказов с именем 3y, этот порядок (ресурс) будет многократно меняться,Состояние ресурса меняется с каждым запросом!

Не по теме:

Сам протокол HTTP являетсяПротокол ресурсно-ориентированного прикладного уровня, но на самом деле есть два разных способа использования протокола HTTP: одинRESTfulДа, он рассматривает HTTP как протокол прикладного уровня и более точно соответствует различным положениям протокола HTTP (Метод, использующий все преимущества HTTP);Другаяэто SOA, он не полностью рассматривает HTTP как протокол прикладного уровня, но рассматривает протокол HTTP как протокол транспортного уровня, а затем устанавливает собственный протокол прикладного уровня поверх HTTP.

Использованная литература:

9.2 Идемпотентность интерфейса

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

Сказав это, каковы преимущества разработки интерфейса как идемпотентного? ? ? ?

Например, поговорим о недостатках неидемпотентности:

  • Когда я был первокурсником в 3 года, я хотел захватить урок физкультуры, но система захвата класса в школе была ужасной (задержка была очень высокой). Я хотел получить класс, поэтому я открыл более 10 вкладок Chrome, чтобы получить его (даже если одна вкладка Chrome выйдет из строя, у меня будут доступны другие вкладки Chrome). Я хочу заняться настольным теннисом или бадминтоном.
  • Когда придет время взять урок, я в свою очередь нажму на настольный теннис или бадминтон, который хочу взять. Если система плохо спроектирована, запрос неидемпотентный (или управление транзакциями некачественное), и моя скорость рук достаточно высока, а сеть достаточно хороша, то, возможно, я взял несколько уроков пинг-понга или бадминтона. (Это необоснованно, человек может пройти только один курс, а я беру несколько или повторные курсы)
  • Сценарий приложения, связанный с торговым центром, может быть следующим: пользователь разместил несколько повторных заказов.

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

  • В фоновом режиме базы данных будет максимум одна запись, и нет явления захвата нескольких курсов.

Проще говоря, идемпотентные интерфейсы предназначены дляпредотвратить дублирование отправки(В базе данных есть несколько повторяющихся данных)!

Некоторые блоггеры в Интернете также поделились несколькими распространенными решениями для дублирования материалов:

  1. Блокировка синхронизации (один поток, возможен сбой в кластере)
  2. Распределенные блокировки, такие как redis (сложная реализация)
  3. Бизнес-поле плюс уникальное ограничение (простое)
  4. Таблица токенов + уникальное ограничение (простая рекомендация)----> Средство реализации идемпотентных интерфейсов
  5. вставка mysql игнорирует или обновляет дубликат ключа (простой)
  6. Общая блокировка + обычный индекс (простой)
  7. Расширение (постановка в очередь) с помощью MQ или Redis
  8. Другие схемы, такие как многоверсионное управление MVCC, оптимистическая блокировка, пессимистическая блокировка, конечный автомат и так далее. .

Использованная литература:

Наконец

Если с вышеизложенным что-то не так или если есть лучший способ понять это, я надеюсь, что вы оставите сообщение в области комментариев. Делайте успехи вместе!

Если вы хотите увидеть большеоригинальныйТехнические статьи, приглашаю всех обратить на меня вниманиеПубличный аккаунт WeChat: Java3y. Также есть публичные аккаунтыОгромные видеоресурсыО, следуйте, чтобы получить его бесплатно.

Ссылки, которые могут быть интересны: