Сокрушительный параллелизм (2): жизненный цикл потоков Java

Java задняя часть

0 Предисловие

Когда поток создается и запускается, он не переходит в состояние выполнения сразу после запуска и не остается в состоянии выполнения все время. В течение жизненного цикла нить проходит черезНовый (New), готовый (Runnable), работающий (Running), блокирующий (Blocked) и мертвый (Dead) 5 состояний. Особенно когда запущен поток, он не всегда может «занимать» ЦП для работы в одиночку, поэтому ЦП необходимо переключаться между несколькими потоками, поэтомуСостояние потока также будет переключаться между выполнением и блокировкой несколько раз..

线程状态转换关系

1 Новое (Новое) состояние

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

  1. В этот момент JVM выделяет для него память и инициализирует значения его переменных-членов;
  2. В этот момент объект потока не проявляет никаких динамических характеристик потока., программа не будет выполнять тело выполнения потока;

2 Готово (работает) состояние

Когда объект потока вызывает метод start(), поток находится всостояние готовности. Ситуация с потоком на данный момент следующая:

  1. В этот момент JVM будетСоздать стек вызовов методов и счетчик программ;
  2. Поток в этом состоянии был вочередь готовности потока(Хотя в виде очереди, на самом деле,назовите это исполняемым пулом вместо исполняемой очереди. Поскольку планирование ЦП не обязательно осуществляется в порядке «первым поступил — первым обслужен»), поток не запускается;
  3. нить в этом местеОжидание, пока система выделит ему квант времени ЦП, это не означает, что метод start() выполняется немедленно;

Вызовите метод start() и метод run() следующим образом:

  1. Вызовите метод start(), чтобы запустить поток, и система будет рассматривать метод run() как тело выполнения потока.. Но если метод run() объекта потока вызывается напрямую, метод run() будет выполняться немедленно, и другие потоки не могут выполняться одновременно, пока метод run() не вернется. Это,Система рассматривает объект потока как обычный объект, и метод run() также является обычным методом, а не телом выполнения потока.;
  2. Следует отметить, что после вызова метода run() потокаПоток больше не находится во вновь созданном состоянии, не вызывайте снова метод start() объекта потока.Метод start() можно вызывать только в потоке в новом состоянии, в противном случае будет возбуждено исключение IllegaIThreadStateExccption.;

Как заставить дочерний поток выполняться сразу после вызова метода start() вместо «ожидания выполнения»:

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

3 Состояние работы

Когда ЦП начинает планироватьсостояние готовностиКогда поток запущен, поток получает квант времени ЦП до того, как он сможет фактически начать выполнение тела выполнения потока метода run(), после чего поток находится в состоянииРабочий статус.

  1. Если компьютер имеет только один ЦП, то в каждый момент времени работает только один поток;
  2. Если на многопроцессорной машине будет несколько потоков, выполняющихся параллельно и в рабочем состоянии;
  3. Когда количество потоков больше, чем количество процессоров, на одном и том же ЦП все равно будет вращаться несколько потоков;

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

  1. Для принятияупреждающая стратегияС точки зрения системы, система выделяет квант времени каждому исполняемому потоку для обработки задачи; когда квант времени израсходован, система лишает ресурс, занимаемый потоком, и позволяет другим потокам получить возможность выполнения. нить будетИз рабочего состояния в состояние готовности, и подождите, пока система снова выделит ресурсы;
  2. Для принятияСовместные стратегииДля системы только тогда, когда поток вызывает свой метод yield(), он отказывается от занятых ресурсов —То есть поток должен активно отдавать занятые ресурсы., нить будетИз рабочего состояния в состояние готовности.

4 Заблокировано (заблокировано) состояние

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

Поток перейдет в состояние блокировки при выполнении следующих условий:

  1. Поток вызывает метод sleep(), добровольно отказаться от занятых ресурсов процессора и временно войти в состояние прерывания (Заблокированные объекты не будут сняты), подождите, пока система выделит ЦП для продолжения выполнения по истечении времени;
  2. Поток, вызывающий блокирующий метод ввода-вывода, поток блокируется до тех пор, пока метод не вернется;
  3. Поток пытается получить монитор синхронизации, но монитор синхронизации удерживается другим потоком;
  4. Программа вызывает метод приостановки потока, чтобы приостановить поток.;
  5. Поток вызывает ожидание, ожидание пробуждения notify/notifyAll (удерживаемая блокировка объекта будет снята);

Классификация состояния блокировки:

  1. ждать блокировки: в рабочем состоянииПоток выполняет метод wait(), чтобы поток перешел в состояние ожидания блокировки;
  2. синхронная блокировка: нить находится вНе удалось получить синхронизированную блокировку синхронизации(поскольку блокировка занята другими потоками), он перейдет в состояние синхронной блокировки;
  3. другая блокировка: вызывая потокsleep() или join() или выдать запрос ввода-вывода, поток перейдет в состояние блокировки. когдаТайм-аут состояния sleep(), join() ожидает завершения потока или тайм-аута, или завершена обработка ввода-выводаКогда поток повторно входит в состояние готовности;

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

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

4.1 Состояние ожидания (WAITING)

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

  1. Потоки, ожидающие выполнения метода wait(), ждут метода notify() или notifyAll();
  2. Поток, ожидающий выполнения метода join(), ожидает окончания выполнения целевого потока и пробуждается;

Как только вышеупомянутые два вида событий разбудят поток, поток войдет вСостояние готовности (РАБОТАЕТ)Продолжать работать.

4.2 Статус TIMED_WAITING

нить входит всостояние ожидания с ограничением по времени,как:

sleep(3000), поток перезапускается после ожидания в течение 3 секундСостояние готовности (РАБОТАЕТ)Продолжать работать.

5 мертвое состояние

Нить закончится следующими тремя способами, после окончания она будет всостояние смерти:

  1. выполнение метода run() или call() завершается, поток заканчивается нормально;
  2. Поток выдает неперехваченное исключение или ошибку;
  3. Вызовите метод остановки потока () напрямую, чтобы завершить поток- этот метод склонен к тупике и, как правило, не рекомендуется;

Объект потока в мертвом состоянии может быть живым, но он больше не является отдельным потоком выполнения.. Как только поток умирает, он не может быть возрожден.Если метод start() вызывается в мертвом потоке, будет выброшено исключение java.lang.IllegalThreadStateException..

Поэтому следует отметить, что:

Как только поток запущен методом start(), он больше не может вернуться в новое (NEW) состояние, а после завершения потока он не может вернуться в состояние готовности (RUNNABLE)..

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

После выполнения поток переходит в состояние TERMINATED.

6 методов, связанных с потоками

public class Thread{
    // 线程的启动
    public void start(); 
    // 线程体
    public void run(); 
    // 已废弃
    public void stop(); 
    // 已废弃
    public void resume(); 
    // 已废弃
    public void suspend(); 
    // 在指定的毫秒数内让当前正在执行的线程休眠
    public static void sleep(long millis); 
    // 同上,增加了纳秒参数
    public static void sleep(long millis, int nanos); 
    // 测试线程是否处于活动状态
    public boolean isAlive(); 
    // 中断线程
    public void interrupt(); 
    // 测试线程是否已经中断
    public boolean isInterrupted(); 
    // 测试当前线程是否已经中断
    public static boolean interrupted(); 
    // 等待该线程终止
    public void join() throws InterruptedException; 
    // 等待该线程终止的时间最长为 millis 毫秒
    public void join(long millis) throws InterruptedException; 
    // 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒
    public void join(long millis, int nanos) throws InterruptedException; 
}

线程方法状态转换

6.1 Переходы потока в готовое, работающее и неактивное состояние

  1. Состояние готовности переходит в рабочее состояние: этот поток получает ресурсы ЦП;
  2. Запуск состояния переход к готовности: этот поток активно вызывает метод yield() или теряет ресурсы ЦП во время работы.
  3. Переходы рабочего состояния в мертвое состояние: выполнение этого потока завершено или возникает исключение;

Уведомление:

Когда вызывается метод yield() в потоке, поток переходит из состояния выполнения в состояние готовности,Но тогда поток в состоянии готовности к планированию ЦП имеет определенную случайностьСледовательно, после того, как A-поток вызывается методом yield(), CPU все равно диспетчеризирует дело A-потока.

6.2 run & start

При вызове start для запуска потока код в методе run выполняется при выполнении потока.

  1. start(): начало потока;
  2. run(): исполнительное тело потока;

6.3 sleep & yield

sleep(): заставить поток спать в течение определенного периода времени через сон (миллис),Метод не может быть разбужен в течение указанного времени и не снимает блокировку объекта.;

Например, мы хотим, чтобы основной поток засыпал каждые 100 миллисекунд перед выводом числа:

/**
 * 可以明显看到打印的数字在时间上有些许的间隔
 */
public class Test1 {  
    public static void main(String[] args) throws InterruptedException {  
        for(int i=0;i<100;i++){  
            System.out.println("main"+i);  
            Thread.sleep(100);  
        }  
    }  
} 

Обратите внимание на следующие моменты:

  1. sleep — это статический метод, лучше не вызывать его с экземпляром объекта Thread.,Потому что он всегда спит текущий поток, а не объект потока, который его вызвал,Он действителен только для объектов потока в рабочем состоянии.. См. пример ниже:
    public class Test1 {  
        public static void main(String[] args) throws InterruptedException {  
            System.out.println(Thread.currentThread().getName());  
            MyThread myThread=new MyThread();  
            myThread.start();  
            // 这里sleep的就是main线程,而非myThread线程 
            myThread.sleep(1000); 
            Thread.sleep(10);  
            for(int i=0;i<100;i++){  
                System.out.println("main"+i);  
            }  
        }  
    }  
    
  2. Планирование потоков Java является ядром многопоточности Java, только хорошее планирование может в полной мере повысить производительность системы и повысить эффективность работы программы. Но вне зависимости от того, как программатор распланирует, только порядок выполнения потока можно только максимизировать, и точно контролировать его нельзя.Потому что после использования метода сна поток переходит в состояние блокировки, только когда время сна закончится, он снова войдет в состояние готовности, а состояние готовности переходит в состояние выполнения, которое контролируется системой, и мы не можем точно вмешиваться в это., поэтому, если вы вызываете Thread.sleep(1000), чтобы перевести поток в спящий режим на 1 секунду, результат может быть больше 1 секунды.
    public class Test1 {  
        public static void main(String[] args) throws InterruptedException {  
            new MyThread().start();  
            new MyThread().start();  
        }  
    }  
      
    class MyThread extends Thread {  
        @Override  
        public void run() {  
            for (int i = 0; i < 3; i++) {  
                System.out.println(this.getName()+"线程" + i + "次执行!");  
                try {  
                    Thread.sleep(50);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    } 
    
    Посмотреть результаты определенной операции: можно обнаружить, что поток 0 выполняется первым, затем поток 1 выполняется один раз и выполняется снова. Обнаружение не выполняется в порядке сна.
    Thread-0线程0次执行!  
    Thread-1线程0次执行!  
    Thread-1线程1次执行!  
    Thread-0线程1次执行!  
    Thread-0线程2次执行!  
    Thread-1线程2次执行!  
    

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

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

public class Test1 {  
    public static void main(String[] args) throws InterruptedException {  
        new MyThread("低级", 1).start();  
        new MyThread("中级", 5).start();  
        new MyThread("高级", 10).start();  
    }  
}  
  
class MyThread extends Thread {  
    public MyThread(String name, int pro) {  
        super(name);// 设置线程的名称  
        this.setPriority(pro);// 设置优先级  
    }  
  
    @Override  
    public void run() {  
        for (int i = 0; i < 30; i++) {  
            System.out.println(this.getName() + "线程第" + i + "次执行!");  
            if (i % 5 == 0)  
                Thread.yield();  
        }  
    }  
}  

Разница между методом sleep() и методом yield() заключается в следующем.:

  1. После того, как метод sleep приостанавливает текущий поток,будет заблокирован, только когда время сна истекло,быть в состоянии готовности. И после того, как метод доходности называется,находится непосредственно в состоянии готовности, поэтому можно просто войти в состояние готовности и перейти в рабочее состояние по расписанию;
  2. объявление метода сна вызывает InterruptedException, поэтому вам нужно поймать исключение при вызове метода сна или явно объявить, что исключение выбрасывается.Метод yield не объявляет об исключении задачи.;
  3. Метод sleep имеет лучшую переносимость, чем метод yield,Как правило, не полагайтесь на метод yield для управления выполнением параллельных потоков.;

6.4 join

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

join имеет 3 перегруженных метода:

void join()    
    当前线程等该加入该线程后面,等待该线程终止。    
void join(long millis)    
    当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度   
void join(long millis,int nanos)    
    等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度

Пример кода, как показано ниже:

/**
 * 在主线程中调用thread.join(); 就是将主线程加入到thread子线程后面等待执行。不过有时间限制,为1毫秒。
 */
public class Test1 {  
    public static void main(String[] args) throws InterruptedException {  
        MyThread t=new MyThread();  
        t.start();  
        t.join(1);//将主线程加入到子线程后面,不过如果子线程在1毫秒时间内没执行完,则主线程便不再等待它执行完,进入就绪状态,等待cpu调度  
        for(int i=0;i<30;i++){  
            System.out.println(Thread.currentThread().getName() + "线程第" + i + "次执行!");  
        }  
    }  
}  
  
class MyThread extends Thread {  
    @Override  
    public void run() {  
        for (int i = 0; i < 1000; i++) {  
            System.out.println(this.getName() + "线程第" + i + "次执行!");  
        }  
    }  
}  

Исходный код метода соединения в JDK выглядит следующим образом:

public final synchronized void join(long millis)    throws InterruptedException {  
    long base = System.currentTimeMillis();  
    long now = 0;  
  
    if (millis < 0) {  
        throw new IllegalArgumentException("timeout value is negative");  
    }  
          
    if (millis == 0) {  
        while (isAlive()) {  
           wait(0);  
        }  
    } else {  
        while (isAlive()) {  
            long delay = millis - now;  
            if (delay <= 0) {  
                break;  
            }  
            wait(delay);  
            now = System.currentTimeMillis() - base;  
        }  
    }  
}  

Метод соединения реализован, вызывая метод ожидания.. Когда основной поток вызывает t.join,Основной поток получит блокировку объекта потока t (ожидание означает получение блокировки объекта) и вызовет ожидание (время ожидания) объекта, пока объект не разбудит основной поток, например, после выхода.Это означает, что когда основной поток вызывает t.join, он должен иметь возможность получить блокировку объекта потока t..

6.5 приостановка и возобновление (устарело)

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

resume-Повторно войти в исполняемое состояние потока.

Почему Thread.suspend и Thread.resume устарели?

Thread.suspend по своей природе подвержен взаимоблокировкам.Если целевой поток приостанавливается, удерживая блокировку на мониторе, защищающем критический системный ресурс, никакие другие потоки не могут получить доступ к ресурсу, пока целевой поток не возобновит работу. Взаимная блокировка возникает, если поток, который хочет возобновить работу целевого потока, пытается заблокировать этот монитор перед вызовом возобновления.. Такие взаимоблокировки обычно проявляются как «замороженные» процессы.

Другая важная информация:

  1. https://blog.csdn.net/dlite/article/details/4212915

6.6 стоп (устарело)

Не рекомендуется и может быть удалено в будущем, так как это небезопасно. Почему Thread.stop устарел?

Потому что это небезопасно по своей сути.Остановка потока заставляет его разблокировать его заблокированным на всех мониторах (монитор выдает ненормальный способ разблокировки ThreadDeath в верхней части стека). Если какие-либо объекты, ранее защищенные этими мониторами, находятся в несогласованном состоянии, другие потоки видят эти объекты в несогласованном состоянии.Такой объект называется поврежденным. Произвольное поведение может возникнуть, когда поток работает с поврежденным объектом. Такое поведение может быть незаметным и трудным для обнаружения, а может быть очевидным.

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

Другая важная информация:

  1. https://blog.csdn.net/dlite/article/details/4212915

6.7 wait & notify/notifyAll

Эти три метода wait и notify/notifyAll являются методами класса Object. Использование wait , notify и notifyAllПредпосылка заключается в том, что блокировка вызывающего объекта получается первой.

  1. После вызова метода ожидания удерживаемая блокировка объекта снимается,Статус потока изменился с «Выполняется» на «Ожидание»., и поместите текущий поток в область объектаочередь ожидания;
  2. После вызова метода notify или notifyAll,Ожидающий поток по-прежнему не выходит из ожидания.После того, как поток, вызвавший noitfy, снимает блокировку, ожидающий поток имеет шанс вернуться из ожидания.;
  3. метод уведомления:Переместить ожидающий поток очереди ожидания из очереди ожидания в очередь синхронизациии метод notifyAll:Переместите все потоки из очереди ожидания в очередь синхронизации, и состояние перемещенного потока изменится с Ожидание на Заблокировано..

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

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

очередь ожидания (пул ожидания): Предполагая, что поток A вызывает метод wait() объекта, поток A снимет блокировку объекта (поскольку метод wait() должен появиться в синхронизированном состоянии, поэтому, естественно, поток A был выполнен раньше, чем метод wait()). выполняется, владеет блокировкой объекта), в то время какПоток А входит в очередь ожидания (пул ожидания) объекта, в это время состояние потока А — Ожидание. Если другой поток вызывает метод notifyAll() того же объекта, тоВсе потоки в пуле ожидания объекта войдут в очередь синхронизации объекта (пул блокировки), готовые конкурировать за владение блокировкой.. Если другой поток вызывает метод notify() того же объекта, тоТолько один поток (случайно) в пуле ожидания объекта войдет в очередь синхронизации объекта (пул блокировки).

Потоки, вызываемые notify или notifyAll, являются регулярными, как показано ниже:

  1. Если поток пробуждается уведомлением, тоПоток, который входит в ожидание первым, будет разбужен первым.;
  2. Если это поток, вызванный nootifyAll, значение по умолчанию равноПоследний, кто войдет, будет разбужен первым, стратегия ЛИФО;

6.8 Приоритет потока

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

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

Класс Thread предоставляет методы setPriority(int newPriority) и getPriority() для установки и возврата приоритета указанного потока Параметр метода setPriority представляет собой целое число в диапазоне от 1 до 10. Вы также можете использовать три предоставленных Класс потока, статические константы:

MAX_PRIORITY   =10
MIN_PRIORITY   =1
NORM_PRIORITY   =5

Пример кода, как показано ниже:

public class Test1 {  
    public static void main(String[] args) throws InterruptedException {  
        new MyThread("高级", 10).start();  
        new MyThread("低级", 1).start();  
    }  
}  
  
class MyThread extends Thread {  
    public MyThread(String name,int pro) {  
        super(name);//设置线程的名称  
        setPriority(pro);//设置线程的优先级  
    }  
    @Override  
    public void run() {  
        for (int i = 0; i < 100; i++) {  
            System.out.println(this.getName() + "线程第" + i + "次执行!");  
        }  
    }  
}  

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

будь осторожен:

Хотя Java предоставляет 10 уровней приоритета, эти уровни приоритета требуют поддержки операционной системы.Разные операционные системы имеют разные приоритеты, и они не очень хорошо соответствуют 10 уровням приоритета Java.. Таким образом, мы должны использовать три статические константы MAX_PRIORITY, MIN_PRIORITY и NORM_PRIORITY для установки приоритета,Это обеспечивает наилучшую переносимость программы..

6.9 Демонические потоки

По сути, нет никакой разницы между потоками демона и обычными потоками.Вызовите метод setDaemon(true) объекта потока, вы можете установить его как поток демона.

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

Подробное описание метода setDaemon:

public final void setDaemon(boolean on): пометить поток как поток демона или пользовательский поток.Виртуальная машина Java завершает работу, когда все запущенные потоки являются потоками демона.

Этот метод должен быть вызван до запуска потока. Метод сначала вызывает метод checkAccess потока без аргументов. Это может вызвать SecurityException (в текущем потоке).

параметр:

on - 如果为 true,则将该线程标记为守护线程。

бросает:

 IllegalThreadStateException - 如果该线程处于活动状态。
 SecurityException - 如果当前线程无法修改该线程。
/** 
* Java线程:线程的调度-守护线程 
*/  
public class Test {  
        public static void main(String[] args) {  
                Thread t1 = new MyCommon();  
                Thread t2 = new Thread(new MyDaemon());  
                t2.setDaemon(true);        //设置为守护线程  
  
                t2.start();  
                t1.start();  
        }  
}  
  
class MyCommon extends Thread {  
        public void run() {  
                for (int i = 0; i < 5; i++) {  
                        System.out.println("线程1第" + i + "次执行!");  
                        try {  
                                Thread.sleep(7);  
                        } catch (InterruptedException e) {  
                                e.printStackTrace();  
                        }  
                }  
        }  
}  
  
class MyDaemon implements Runnable {  
        public void run() {  
                for (long i = 0; i < 9999999L; i++) {  
                        System.out.println("后台线程第" + i + "次执行!");  
                        try {  
                                Thread.sleep(7);  
                        } catch (InterruptedException e) {  
                                e.printStackTrace();  
                        }  
                }  
        }  
}  

Результаты:

后台线程第0次执行!  
线程1第0次执行!  
线程1第1次执行!  
后台线程第1次执行!  
后台线程第2次执行!  
线程1第2次执行!  
线程1第3次执行!  
后台线程第3次执行!  
线程1第4次执行!  
后台线程第4次执行!  
后台线程第5次执行!  
后台线程第6次执行!  
后台线程第7次执行! 

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

Фактически:Стандарт JRE для определения того, завершено ли выполнение программы, заключается в том, что все потоки выполнения переднего плана завершены, независимо от состояния фоновых потоков, поэтому вы должны обратить внимание на эту проблему при использовании фоновых потоков..

6.10 Как закончить нить

Thread.stop(), Thread.suspend, Thread.resume, Runtime.runFinalizersOnExitЭти методы завершения потока устарели и крайне небезопасны в использовании! Чтобы безопасно и эффективно завершить поток, используйте следующие методы.

  1. Выполните метод run в обычном режиме, а затем завершите его;
  2. Управляйте условием цикла и идентификатором условия оценки, чтобы завершить поток;

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

class MyThread extends Thread {  
    int i=0;  
    @Override  
    public void run() {  
        while (true) {  
            if(i==10)  
                break;  
            i++;  
            System.out.println(i);  
              
        }  
    }  
}  
或者
class MyThread extends Thread {  
    int i=0;  
    boolean next=true;  
    @Override  
    public void run() {  
        while (next) {  
            if(i==10)  
                next=false;  
            i++;  
            System.out.println(i);  
        }  
    }  
}  
或者
class MyThread extends Thread {  
    int i=0;  
    @Override  
    public void run() {  
        while (true) {  
            if(i==10)  
                return;  
            i++;  
            System.out.println(i);  
        }  
    }  
}  

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

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

public final void wait() throws InterruptedException 
public static native void sleep(long millis) throws InterruptedException
public final void join() throws InterruptedException

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

Каждый поток имеет прерванное состояние, которое по умолчанию равно false.. Состояние прерывания потока можно определить методом isInterrupted() объекта Thread. Статус прерывания можно установить в TRUE через метод Interrupt() объекта Thread.

Когда нить находится в трех состояниях сна, подождите, присоединяйтесь,Если в это время его статус прерывания истинен, то будет выброшено исключение InterruptedException., и сбросить состояние прерывания на false.

См. следующий простой пример:

public class Test1 {  
    public static void main(String[] args) throws InterruptedException {  
        MyThread thread=new MyThread();  
        thread.start();  
    }  
}  
  
class MyThread extends Thread {  
    int i=1;  
    @Override  
    public void run() {  
        while (true) {  
            System.out.println(i);  
            System.out.println(this.isInterrupted());  
            try {  
                System.out.println("我马上去sleep了");  
                Thread.sleep(2000);  
                this.interrupt();  
            } catch (InterruptedException e) {  
                System.out.println("异常捕获了"+this.isInterrupted());  
                return;  
            }  
            i++;  
        }  
    }  
}  

Результаты теста:

1  
false  
我马上去sleep了  
2  
true  
我马上去sleep了  
异常捕获了false 

Как видите, сначала выполняется первый цикл while, в первом цикле засыпаем на 2 секунды, а затем устанавливаем состояние прерывания в true.При входе во второй цикл состояние прерывания первое устанавливается в true, при повторном переходе в спящий режим сразу выбрасывается исключение InterruptedException, а затем оно перехватывается нами. Затем состояние прерывания снова автоматически сбрасывается в false (как видно из последнего вывода).

Итак, мы можем использовать метод прерывания для завершения потока. Конкретное использование заключается в следующем:

public class Test1 {  
    public static void main(String[] args) throws InterruptedException {  
        MyThread thread=new MyThread();  
        thread.start();  
        Thread.sleep(3000);  
        thread.interrupt();  
    }  
}  
  
class MyThread extends Thread {  
    int i=0;  
    @Override  
    public void run() {  
        while (true) {  
            System.out.println(i);  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                System.out.println("中断异常被捕获了");  
                return;  
            }  
            i++;  
        }  
    }  
} 

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

0  
1  
2  
中断异常被捕获了

или

0  
1  
2  
3  
中断异常被捕获了 

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

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

второй случай, когда основной поток выходит из 3-секундного состояния сна и вызывает метод прерывания дочернего потока, а дочерний поток в это время не находится в состоянии сна. Затем в третьем цикле while он переходит в состояние сна и сразу же выдает InterruptedException.