Высокопараллельное программирование от начального уровня до мастерства (2)

Java

Наиболее часто ругаемое место на собеседованиях должно иметь знание параллельного программирования, будь вы новичок, который только начинает или CRUD-фрик с 2-3 годами опыта, а это значит, что вам зададут такой вопрос на минимум 3 года., почему бы не потратить время на борьбу до конца. Лучший способ избавиться от страха — встретиться с ним лицом к лицу, Олли! (Эта серия представляет собой заметки и резюме моего учебного процесса, а также содержит отладочный код, в который может играть каждый)

обзор последней главы

1. Какую основную проблему решает параллельное программирование?

2. Какими способами создаются и реализуются потоки?

3. Как работает new Thread() тремя способами?

Пожалуйста, просмотрите приведенные выше вопросы самостоятельно, если у вас все еще есть какие-либо вопросы, пожалуйста,Пересмотрите предыдущую главу~

Краткое содержание главы

После завершения этой главы вы生命周期Иметь четкое представление и понимать, как выполнять преобразование между различными состояниями, и интерпретировать класс перечисления состояния потока Java. Кроме того, в этой главе основное внимание будет уделеноThread.start()Проведите детальную интерпретацию и анализ, и в то же время вы сможете более четко разобраться в кейсеstart()а такжеrun()Отношение между. (Старые правила, учащиеся, знакомые с этой темой, могут поставить лайк прямо 👍, чтобы завершить эту главу)

Загрузка кода этой главы

Во-первых, жизненный цикл потока

Я думаю, что многие студенты не сердятся, когда видят эти четыре слова, их всегда об этом спрашивают на собеседованиях.生命周期что это такое生命周期Если вы опишете это, ваш гнев всплывет. Это правда, что это немного раздражает, но у меня нет никаких навыков.死记硬背Это самый простой и лучший способ. Когда вы знакомы с ним, вы, естественно, поймете его при использовании и даже получите свои собственные уникальные идеи. Не говорите слишком много, просто запомните концептуальные вещи и подавайте еду~

1.НОВЫЙ этап

НОВЫЙ этап это тыnew Thread()Этап создания объекта потока.

фокус

На этапе NEW потока вообще не существует, мы просто создалиTherad对象, точно так же, как наш наиболее часто используемыйnewКлючевое слово - правда. Только когда мы действительно запустим поток, наш поток будет создан в процессе JVM.

Согласно идее из предыдущей главы, после того, как мы создадим новый объект Thread, нам нужно вызватьThread.start()чтобы запустить поток, после чего поток начнется сNEWфазовый переход кRUNNABLEсцена.

2. Запускаемая фаза

ТолькопередачаThread.start()метод включения потока изNEWфазовый переход кRUNNABLEсцена.

Конечно, мы также можем знать из буквального значения, что нить находится в可执行转状态Вместо реального состояния выполнения поток в это время может только ждать, пока ЦП перевернет марку, и тогда он может действительно работать. Некоторые учащиеся могут сказать, что, если ЦП не переключает марку все время? Строго говоря, вRUNNABLEЕсть только два выхода из нити, один线程意外退出, а один проваливается процессоромRUNNINGсцена.


Здесь все выглядит так просто и красиво, создайте новый объект Thread, затем вызовите start для его запуска, а затем введите после включения процессора.RUNNINGсцена. Например,NEWВо время этапа наша нить по-прежнему представляет собой красивый объект за пределами дворца, призывающийstartПосле метода он превратился в маленького мастера во дворце, что является средней стадией.RUNNABLE, когда будет получено право на выполнение планирования ЦП, оно будет повышено до предпочтительного娘娘, то есть ввестиRUNNINGсцена. Вполне возможно, что в это время наша нить娘娘Должно быть, это намного сложнее, чем красота за пределами дворца.


3. БЕГОВОЙ этап

⚠️Внимание

У студентов, которые поняли этот контент, могут возникнуть сомнения, когда они увидят это. В состоянии java-потока такого состояния нет. Почему мы выделяем это состояние для объяснения, когда говорим о жизненном цикле? Для беглости содержания главы объяснение этого содержания будет объяснено в следующем разделе, а мы продолжим говорить о нашем здесь.线程生命周期.


хорошо давайте продолжим

Поток на этом этапе получил право на выполнение планирования ЦП, то есть он находится в рабочем состоянии.

На этом этапе поток может переходить вперед или назад:

1. Поскольку опрос планировщика ЦП приводит к тому, что поток прекращает выполнение, он войдет вRUNNABLEсцена.

2. Активный вызов потокаyield, отказаться от выполнения процессора вправо, он войдетRUNNABLEсцена(这种方式并不是百分百生效的,在CPU资源不紧张的时候不会生效).

3. позвонитьsleep,waitметод, введитеBLOCKEDсцена(这里讲的BLOCKED阶段和线程的BLOCKED状态需要区分开,这边讲的是一个比较广义的BLOCKED的阶段)

4. Введите блокирующую операцию ввода-выводаBLOCKEDсцена

5. Чтобы получить ресурс блокировки, присоедините замок к очереди блокировки и введитеBLOCKEDсцена

6. Выполнение потока завершается или вызываетсяstopметодом или судя по логическому идентификатору, введите напрямуюTERMINATEDсцена

4. ЗАБЛОКИРОВАННАЯ стадия

Причина выхода на этот этап уже вRUNNINGЭтап был объяснен, и здесь он не будет объясняться.Здесь мы в основном познакомим вас с тем, как можно переключать потоки на этом этапе. 1. Перейти напрямуюTERMINATED, например, вызовstopметод или неожиданная смерть (сбой JVM)

2. Операция блокировки потока завершается, и данные, которые вы хотите обработать, считываются или записываются вRUNNABLEусловие

3. Поток завершает сон за указанное время и входитRUNNABLEусловие

4.WaitСостояние потокаnotifyилиnotifyallпросыпайся, входиRUNNABLEусловие

5. Получите ресурс блокировки и введитеRUNNABLEусловие

6. Процесс блокировки потока прерывается, например вызовinterruptметод, введитеRUNNABLEусловие

5. Статус ЗАВЕРШЕН

TERMINATEDСостояние — это конечное состояние потока, в котором поток не переключается ни в какое другое состояние, поток входит вTERMINATEDСостояние означает, что весь жизненный цикл потока завершен, введитеTERMINATEDСтатус бывает трех видов:

1. Нить нормально заканчивается

2. Тема неожиданно заканчивается

3.JVM Crash

2. Как соотносятся жизненный цикл потока и состояние потока Java (анализ исходного кода)

Столкнувшись с этой проблемой, самым прямым способом демонстрации будет просмотр кода. Сначала найдите код.java.lang.Thread.StateЭтот перечислимый класс.

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

можно увидеть здесьstateКласс перечисления содержит состояния потока 6. Согласно описанию, мы будем интерпретировать эти шесть состояний одно за другим.

(1) НОВОЕ состояние = НОВЫЙ этап

     * <li>{@link #NEW}<br>
     *     A thread that has not yet started is in this state.
     *     </li>

В исходном коде четко указаноNEWсостояние一个线程刚刚被创建,但是还没有启动地时候所处的状态, это то же самое, что и наш предыдущий подразделNEWЗдесь нечего сказать, если этапы могут соответствовать.

(2) Состояние РАБОТАЕТ = стадия РАБОТАЕТ + стадия РАБОТАЕТ

 * <li>{@link #RUNNABLE}<br>
     *     A thread executing in the Java virtual machine is in this state.
     *     </li>

Описание этого абзаца означает, что состояние потока, выполняющегося в виртуальной машине java, называетсяRUNNABLE. То есть мы объяснили отдельно в предыдущем разделеRUNNABLE阶段а такжеRUNNING阶段В состоянии потока вызывается однородноRUNNABLE状态, причина, по которой мы разделяем два состояния при разделении жизненного цикла, заключается в том, что эти два состояния все же очень разные.RUNNINGотношение потока состоянияRUNNABLEПотоки состояния немного сложнее.

(3) Состояние BLOCKED + состояние WAITING + состояние TIMED_WAITING = стадия BLOCKED

 * <li>{@link #BLOCKED}<br>
     *     A thread that is blocked waiting for a monitor lock
     *     is in this state.
     *     </li>
     * <li>{@link #WAITING}<br>
     *     A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     </li>
     * <li>{@link #TIMED_WAITING}<br>
     *     A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *     </li>

Исходный код для этих трех состояний описывается следующим образом.

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

WAITING: в этом состоянии находится поток, бесконечно ожидающий, пока другой поток выполнит определенное действие.

TIMED_WAITING: поток, ожидающий выполнения операции другим потоком, находится в этом состоянии в течение указанного времени ожидания.

студенты дляBLOCKEDа такжеWAITINGЯсна ли разница между этими двумя состояниями? Четко введите (4) напрямую~

Мы по-прежнему передаем исходный код дляstateОписание значения перечисления для ввода темы. государство заBLOCKEDШтаты описываются следующим образом:

 /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED

исходный код дляBLOCKEDОписание этого состояния следующее: поток ожидает полученияmonitor lockкогда нить находится вBLOCKED. Проще говоря, этоsynchronizedСинхронизированный блок или метод, описываемый ключевым словом. В это время, когда другие потоки хотят получить описанный синхронизированный блок или метод, этот поток войдет и будет ждать получения.monitor lockкогда вы входитеBLOCKEDусловие. Ключ здесьmonitor lockМониторные замки.

Метод пробуждения — блокировка целевого монитора.monitor lockАктивный релиз, в это время мы соревнуемся за блокировку мониторинга и добиваемся успеха.

состояние дляWAITINGСостояния описываются следующим образом:

/**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING

Здесь мы описываем, при каких обстоятельствах наш поток войдетWAITINGстатус, соответственно звонитObject.wait,Thread.joinа такжеLockSupport.parkВойдут три интерфейса APIWAITINGСтатус, API потоков здесь подробно описываться не будет, он будет представлен в отдельной главе позже, здесь мы не будем о нем беспокоиться.

также объяснилWAITINGСтатус — это ключ к моменту, когда поток ожидает завершения определенной операции другим потоком.两个线程,目标线程等待另一个线程完成某个动作.

Метод пробуждения состоит в том, чтобы дождаться, пока другие потоки завершат свою собственную логику, а затем вызватьnotifyилиnotiffyallпроснуться вWAITINGсостояние нити.

(4) Состояние TERMINATED = состояние TERMINATED


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

Сосредоточьтесь на этом, посмотрите, как усталые одноклассники делают перерыв, а затем анализ исходного кода start()

3. Интерпретация исходного кода шаблона проектирования Thread.start()

Прежде чем мы начнем, давайте подумаем, почемуstart()можно начать нашу тему,run()не можешь?

Не говори глупостей, сначала зайди в исходный код

/**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

Старая рутина, давайте сначала посмотрим на метод

/**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */

переводить:start()Метод запускает поток, и виртуальная машина Java вызывает егоrun()метод для выполнения логической единицы потока. И потоку разрешено запускаться только один раз, это незаконно запускать поток несколько раз, он выдастIllegalThreadStateExceptionаномальный.

Ниже мы объясним через три шагаThread.start()

(1) Определить значение threadStatus

 /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

threadStatus==0выражатьNEWсостояние, если звонитThread.start()узнать, когдаthreadStatus!=0то это означает, что потока больше нет вNEWсостояние, вызов этого метода в настоящее время незаконен, поэтому бросьтеIllegalThreadStateExceptionаномальный. (Такое простое логическое суждение, я считаю, что у моих одноклассников должно быть такое же представление, как и у меня, "я тоже так умею 😁")

(2) Присоединяйтесь к группе тем

       /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

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

void add(Thread t) {
        synchronized (this) {
            if (destroyed) {
                throw new IllegalThreadStateException();
            }
            if (threads == null) {
                threads = new Thread[4];
            } else if (nthreads == threads.length) {
                threads = Arrays.copyOf(threads, nthreads * 2);
            }
            threads[nthreads] = t;

            // This is done last so it doesn't matter in case the
            // thread is killed
            nthreads++;

            // The thread is now a fully fledged member of the group, even
            // though it may, or may not, have been started yet. It will prevent
            // the group from being destroyed so the unstarted Threads count is
            // decremented.
            nUnstartedThreads--;
        }
    }

addМетод делится на три шага

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

 if (destroyed) {
                throw new IllegalThreadStateException();
            }

Шаг 2: Добавьте поток в массив групп потоков.

if (threads == null) {
                threads = new Thread[4];
            } else if (nthreads == threads.length) {
                threads = Arrays.copyOf(threads, nthreads * 2);
            }
            threads[nthreads] = t;

            // This is done last so it doesn't matter in case the
            // thread is killed
            nthreads++;

Судите первымthreadsЕсли он пуст, массив создается, если он пуст, и начальная длина равна 4. еслиthreadsНе пустой, суждениеnthreads == threads.lengthЕсли true, развернуть массив потоковthreads = Arrays.copyOf(threads, nthreads * 2), добавьте входной параметр вthreads[], индексы накапливаютсяnthreads++

Шаг 3. Уменьшите количество незапущенных потоков на один

// The thread is now a fully fledged member of the group, even
            // though it may, or may not, have been started yet. It will prevent
            // the group from being destroyed so the unstarted Threads count is
            // decremented.
            nUnstartedThreads--;

(3) Вызовите метод start0(), чтобы запустить поток

 boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
        

startedЭто состояние запуска потока, значение по умолчанию — false, запуск успешен, а затем ему присваивается значение true. тогда позвониstart0()метод

    private native void start0();

В этот момент мы вспоминаем, что логика выполнения нашего потока написана наrun(), то весь процесс находит, что звонить некудаrun(),только этоstart0()Способ самый подозрительный. Видно, что этот метод является本地方法,существуетThread.javaВ начале определения класса есть статические блоки для завершения регистрации.

 /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

ОК, остановись здесь, мы будем привлекать некоторый контент в нижней части JVM, мы не будем делать расширение здесь, нам просто нужно знатьstart()внутренний звонок звонокstart0()метод, который, наконец, вызывается внутри потока после создания нашего потока.run()метод.

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

На данный момент мы должны четко понимать проблему в начале этого раздела,为什么start()能启动我们的线程,run()不行呢.相信大家已经知道了原因了,因为runМожет использоваться только как внутренний класс реализации, если вы просто вызываетеrunмы просто реализовали логику в этом потоке и на самом деле не открывали новый поток для выполнения нашей логики. Для открытия нового потока требуется вызовstart0()Позвольте JVM сделать за нас создание, так что звоните толькоstart()метод запуска потока.

Здесь я также написал небольшой пример для проверки:

 public static class TestStartAndRun implements Runnable{

    @Override
    public void run() {

      System.out.println("我的名字叫"+Thread.currentThread().getName());
    }
  }


  public static void main(String[] args) {

    new Thread(new TestStartAndRun(),"start").start();

    new Thread(new TestStartAndRun(),"run").run();

  }

выход:

我的名字叫start
我的名字叫main

Можно видеть, что наше определенное имя под названием «Беги» не запускается, и это наш собственный.mainТема, это доказываетrunНевозможно создать поток, но можно выполнять внутренние классы в текущем потокеrun, и может выполняться несколько раз.

В-четвертых, расширенное чтение - имитируйте шаблон проектирования стартового шаблона, чтобы написать аналогичную программу самостоятельно.

/**
   * 杯子
   */
  final void Teacup(String nothing){
    System.out.print("今天");
    Brewing(nothing);
    System.out.println(",愉快的一天开始啦!");
  }

  /**
   * 泡点什么呢
   * @param nothing
   */
  void Brewing(String nothing){
  }

  public static void main(String[] args) {
    TemplateDesignExample t1 = new TemplateDesignExample(){
      @Override
      void Brewing(String nothing){
        System.out.print("下雨,"+nothing);
      }
    };
    t1.Teacup("早上喝咖啡");


    TemplateDesignExample t2 = new TemplateDesignExample(){
      @Override
      void Brewing(String nothing){
        System.out.print("晴天,"+nothing);
      }
    };
    t2.Teacup("早上喝茶");
  }

выход:

今天下雨,早上喝咖啡,愉快的一天开始啦!
今天晴天,早上喝茶,愉快的一天开始啦!

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

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