Переходы состояний потока и основные операции

интервью Java задняя часть Операционная система
Переходы состояний потока и основные операции

Оригинальная статья, краткое изложение опыта и жизненные перипетии на всем пути от набора в школу до фабрики А

Нажмите, чтобы узнать подробностиwww.codercc.com

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

1. Создайте новую тему

Java-программа начинает выполняться из метода main(), а затем выполняется в соответствии с установленной логикой кода.Кажется, что никакие другие потоки не задействованы, но на самом деле java-программа по своей природе является многопоточной программой, включающей: (1) Процесс распределения отправляется в JVM Поток сигнала; (2) поток, вызывающий метод finalize объекта; (3) поток, очищающий ссылку; (4) основной поток, запись точки пользовательской программы. Итак, как создать новый поток в пользовательской программе, пока есть три способа:

  1. Переопределите метод запуска, унаследовав класс Thread;

  2. Реализуя работающий интерфейс;

  3. Реализуя три метода вызываемого интерфейса, давайте посмотрим на конкретную демонстрацию ниже.

     public class CreateThreadDemo {
     
         public static void main(String[] args) {
             //1.继承Thread
             Thread thread = new Thread() {
                 @Override
                 public void run() {
                     System.out.println("继承Thread");
                     super.run();
                 }
             };
             thread.start();
             //2.实现runable接口
             Thread thread1 = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     System.out.println("实现runable接口");
                 }
             });
             thread1.start();
             //3.实现callable接口
             ExecutorService service = Executors.newSingleThreadExecutor();
             Future<String> future = service.submit(new Callable() {
                 @Override
                 public String call() throws Exception {
                     return "通过实现Callable接口";
                 }
             });
             try {
                 String result = future.get();
                 System.out.println(result);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             } catch (ExecutionException e) {
                 e.printStackTrace();
             }
         }
     
     }
    

В комментариях выше подробно описаны три способа создания новой темы. Основные из них:

  • Поскольку Java не может реализовать несколько интерфейсов, необходимо максимально учитывать реализацию интерфейсов при создании потоков;
  • Реализуйте вызываемый интерфейс и отправьте его в ExecutorService, чтобы вернуть результат асинхронного выполнения. Кроме того, вы обычно можете использовать FutureTask (Callable callable), чтобы обернуть вызываемый объект, а затем отправить FeatureTask в ExecutorsService. Как показано на рисунке:
    FutureTask接口实现关系

Кроме того, поскольку FeatureTask также реализует интерфейс Runable, вы также можете использовать второй описанный выше метод (реализующий интерфейс Runable) для создания нового потока;

  • Runable может быть преобразован в Callable с помощью Executors, конкретные методы: Callable callable (Runnable task, T result), Callable callable (Runnable task).

    2. Переход состояния потока

    线程状态转换图

    Это изображение взято из книги "Искусство параллельного программирования JAVA". Потоки будут переходить между различными состояниями. Диаграмма перехода потока потока Java показана на рисунке выше. После создания потока вызывается метод start() для запуска.При вызове методов wait(), join() и LockSupport.lock() поток переходит вWAITINGstate, и те же wait(long timeout), sleep(long), join(long), LockSupport.parkNanos(), LockSupport.parkUtil() добавляют функцию ожидания таймаута, то есть поток войдет после вызова этих методыTIMED_WAITINGсостояние, когда наступит время ожидания тайм-аута, поток переключится в состояние Runable.Кроме того, когда состояния WAITING и TIMED _WAITING находятся в состоянии, поток может быть преобразован в состояние Runable через Object.notify(), Методы Object.notifyAll(). Когда поток конкурирует за ресурсы, т. е. ожидает получения блокировки, поток входит вBLOCKEDСостояние блокировки, когда поток получает блокировку, поток переходит в состояние Runable. После того, как поток завершает работу, поток входит вTERMINATEDСостояние, изменение состояния можно назвать жизненным циклом потока. Также обратите внимание, что:

    • Когда поток входит в синхронизированный метод или блок синхронизированного кода, поток переключается в состояние BLOCKED, а при использовании блокировки в java.util.concurrent.locks для блокировки поток переключается в состояние WAITING или TIMED_WAITING, поскольку блокировка будет вызовите методы LockSupport.

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

    JAVA线程的状态

    3. Основные операции состояния потока

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

    3.1. interrupted

    Прерывание можно понимать как бит флага потока, который указывает, был ли запущенный поток прерван другими потоками. Прерывание — это как поздороваться с другим потоком. Другие потоки могут вызвать метод interrupt() потока, чтобы прервать его, а поток может вызвать isInterrupted(), чтобы определить прерванную работу других потоков самостоятельно, чтобы отреагировать. Кроме того, статический метод Thread также может быть вызван interrupted() прерывает текущий поток, этот метод очистит бит флага прерывания.Следует отметить, что при выдаче InterruptedException флаг прерывания будет очищен, что означает, что вызов isInterrupted вернет false.

    线程中断的方法

    Рассмотрим следующие конкретные примеры

    public class InterruptDemo {
        public static void main(String[] args) throws InterruptedException {
            //sleepThread睡眠1000ms
            final Thread sleepThread = new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    super.run();
                }
            };
            //busyThread一直执行死循环
            Thread busyThread = new Thread() {
                @Override
                public void run() {
                    while (true) ;
                }
            };
            sleepThread.start();
            busyThread.start();
            sleepThread.interrupt();
            busyThread.interrupt();
            while (sleepThread.isInterrupted()) ;
            System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());
            System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());
        }
    }
    

    выходной результат

    sleepThread isInterrupted: false busyThread isInterrupted: true

    Включены два потока, sleepThread и BusyThread, sleepThread приостанавливается на 1 с, а BusyThread выполняет бесконечный цикл. Затем прервите два потока соответственно.Видно, что sleepThread сбрасывает флаг после создания InterruptedException, в то время как busyThread не сбрасывает флаг.

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

    3.2. join

    Метод соединения можно рассматривать как способ взаимодействия между потоками. Во многих случаях ввод одного потока может сильно зависеть от вывода другого потока. Это как два хороших друга. Один из них идет вперед и вдруг видит др. Если базовый друг отстает, в это время он будет ждать базового друга на том же месте, а когда базовый друг догонит, они пойдут рука об руку. На самом деле такое сотрудничество между потоками также соответствует реальной жизни. В процессе разработки программного обеспечения, после получения требований от клиентов, оно должно быть разложено аналитиками требований.В это время разработка продукта будет продолжаться. Если экземпляр потока A выполняет threadB.join(), это означает, что текущий поток A будет ждать завершения потока threadB, прежде чем поток A продолжит выполнение. Что касается метода соединения, предоставляются следующие методы:

    public final synchronized void join(long millis) public final synchronized void join(long millis, int nanos) public final void join() throws InterruptedException

    В дополнение к методу join() класс Thread также предоставляет метод ожидания тайм-аута.Если поток threadB не завершится в течение времени ожидания, threadA продолжит выполнение после тайм-аута. Ключ к исходному коду метода соединения:

     while (isAlive()) {
        wait(0);
     }
    

    Видно, что текущий ожидающий объект threadA всегда будет блокироваться до конца ожидающего объекта threadB, то есть, когда isAlive() вернет false, цикл while завершится, а когда threadB выйдет, будет вызван метод notifyAll() чтобы уведомить все ожидающие потоки. В следующем примере используется конкретный пример, иллюстрирующий использование метода соединения:

    public class JoinDemo {
        public static void main(String[] args) {
            Thread previousThread = Thread.currentThread();
            for (int i = 1; i <= 10; i++) {
                Thread curThread = new JoinThread(previousThread);
                curThread.start();
                previousThread = curThread;
            }
        }
    
        static class JoinThread extends Thread {
            private Thread thread;
    
            public JoinThread(Thread thread) {
                this.thread = thread;
            }
    
            @Override
            public void run() {
                try {
                    thread.join();
                    System.out.println(thread.getName() + " terminated.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    Результат:

    main terminated. Thread-0 terminated. Thread-1 terminated. Thread-2 terminated. Thread-3 terminated. Thread-4 terminated. Thread-5 terminated. Thread-6 terminated. Thread-7 terminated. Thread-8 terminated.

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

    3.3 sleep

    Общедоступный метод static native void sleep(long millis), очевидно, является статическим методом Thread. Очевидно, что он усыпляет текущий поток на заданное время. Точность его времени сна зависит от таймера и планировщика процессора. Следует отметить, что если текущий поток получает блокировку, метод сна не теряет блокировку. Метод sleep часто используется для сравнения цен с методом Object.wait(), который часто задают на собеседованиях.

    sleep() VS wait()

    Основное различие между ними:

    1. Метод sleep() — это статический метод Thread, а wait — метод экземпляра Object.
    2. Метод wait() должен вызываться в синхронизированном методе или синхронизированном блоке, то есть должна быть получена блокировка объекта. Метод sleep() не имеет этого ограничения и может использоваться где угодно. Кроме того, метод wait() снимает блокировку занятого объекта, так что поток входит в пул ожидания и ожидает следующего получения ресурса. Метод sleep() просто освобождает ЦП и не снимает блокировку объекта;
    3. Метод sleep() будет продолжать выполняться, если квант времени ЦП снова получен после достижения времени сна, в то время как метод wait() должен дождаться уведомления Object.notift/Object.notifyAll, прежде чем выйти из пула ожидания и получить Срез процессорного времени снова будет продолжен.

    3.4 yield

    public static native void yield(); Это статический метод. После выполнения он уступит ЦП текущему потоку. Однако следует отметить, что уступка ЦП не означает, что текущий поток больше не работает. соревнования, текущий поток, получивший квант времени ЦП, будет продолжать работать. Кроме того, уступленный квант времени будет выделен толькоДайте текущему потоку такой же приоритетразгром. Что такое приоритет потока? Поговорим об этом подробно ниже.

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

    В Java-программе черезЦелочисленная переменная-член PriorityДля управления приоритетом диапазон приоритета составляет от 1 до 10. При построении потока он может быть установлен методом **setPriority(int)**.Приоритет по умолчанию равен 5, а поток с высоким приоритетом ниже, чем приоритет.Потоки имеют приоритет, чтобы получить квант процессорного времени. Следует отметить, что существуют различия в планировании потоков на разных JVM и операционных системах, а некоторые операционные системы даже игнорируют настройку приоритета потока.

    Кроме того, следует отметить, что методы sleep() и yield() также являются текущим потоком, который будет передавать ресурсы процессора, но разница между ними в том, что другие потоки могут конкурировать за квант времени, переданный sleep( ), то есть сказано, что есть шанс получить квант времени, отданный текущим потоком. Метод yield() позволяет только потокам с тем же приоритетом, что и текущий поток, получать освобожденный квант времени процессора.

    4. Демоническая нить

    Поток демона — это особый вид потока, как и его название, он является хранителем системы, молча охраняя некоторые системные службы в фоновом режиме, такие как поток сборки мусора, поток JIT может понимать поток демона. Ему соответствует пользовательский поток, который можно рассматривать как системный рабочий поток, который будет выполнять бизнес-операции всей системы. После того, как пользовательский поток полностью завершится, это означает, что бизнес-задачи всей системы завершены, поэтому в системе нет объектов для охраны, и поток демона, естественно, уйдет. Когда приложение Java имеет только потоки демона, виртуальная машина завершится естественным образом. Ниже приведен простой пример, описывающий использование потоков Daemon.

    public class DaemonDemo {
        public static void main(String[] args) {
            Thread daemonThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            System.out.println("i am alive");
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            System.out.println("finally block");
                        }
                    }
                }
            });
            daemonThread.setDaemon(true);
            daemonThread.start();
            //确保main线程结束前能给daemonThread能够分到时间片
            try {
                Thread.sleep(800);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    Результат:

    i am alive finally block i am alive

    В приведенном выше примере метод запуска daemodThread представляет собой бесконечный цикл while, который всегда будет печатать, но когда основной поток завершится, daemonThread завершится, поэтому бесконечного цикла не будет. Основной поток сначала спит в течение 800 мс, чтобы гарантировать, что daemonThread может иметь возможность временного среза, то есть он может нормально выполнять операцию печати «я жив» и операцию «finally block» в блоке finally. Сразу после завершения основного потока происходит выход из daemonThread, в это время печатается только «я жив», а финальный блок не печатается. Поэтому здесь следует отметить, чтоПоток демона не будет выполнять код в блоке finnaly при выходе, поэтому выполнять такие операции, как освобождение ресурсов в блоке finnaly, небезопасно.

    Поток может установить поток как поток демона с помощью метода setDaemon(true). И следует отметить, что установка потока демона должна предшествовать методу start(), иначе он сообщит

    Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.setDaemon(Thread.java:1365) at learn.DaemonDemo.main(DaemonDemo.java:19)

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