Dieser Artikel stellt hauptsächlich den Lebenszyklus und die Statuskontrolle von Java-Threads vor. Freunde in Not können sich auf
1. Thread beziehen Lebenszyklus
Thread-Zustandsübergangsdiagramm:
Neuer Zustand
Nachdem das Schlüsselwort new und die Thread-Klasse oder ihre Unterklasse zum Erstellen eines Thread--Objekts verwendet wurden, befindet sich das Thread-Objekt in einem neuen Zustand. Der Thread im neuen Zustand verfügt über einen eigenen Speicherplatz und wechselt durch Aufrufen der Startmethode in den Bereitschaftszustand (ausführbar).
Hinweis: Die start()-Methode kann in einem bereits gestarteten Thread nicht erneut aufgerufen werden, da sonst eine Javalang.IllegalThreadStateException-Ausnahme auftritt.
2. Bereitzustand
Der Thread im Bereitschaftszustand hat die Betriebsbedingungen, wurde aber noch nicht der CPU zugewiesen Thread ist bereit Warteschlange (Obwohl es sich um eine Warteschlange handelt, wird es tatsächlich als ausführbarer Pool und nicht als ausführbare Warteschlange bezeichnet. Da die Planung der CPU nicht unbedingt in einer ersten Phase geplant ist) In, First-Out-Reihenfolge) ist das Wartesystem Es weist CPU zu. Der Wartestatus ist nicht der Ausführungsstatus. Wenn das System ein auf die Ausführung wartendes Thread-Objekt auswählt, wechselt es vom Warteausführungsstatus in den Ausführungsstatus. Die vom System ausgewählte Aktion wird als „CPU-Planung“ bezeichnet. Sobald der Thread die CPU erhält, wechselt er in den Ausführungsstatus und ruft automatisch seine eigene Ausführungsmethode auf.
Tipp: Wenn Sie möchten, dass der Sub-Thread sofort nach dem Aufruf der start()-Methode ausgeführt wird, können Sie die Thread.sleep()-Methode verwenden, um den Haupt-Thread für eine Weile in den Ruhezustand zu versetzen und dann den Sub-Thread auszuführen -Faden.
3. Ausführungsstatus
Der Thread im Ausführungsstatus ist am komplexesten und kann blockiert, bereit und tot werden. Wenn ein Thread im Bereitschaftszustand von der CPU geplant wird, wechselt er vom Bereitschaftszustand in den Ausführungszustand und führt die Aufgaben in der run()-Methode aus. Wenn der Thread CPU-Ressourcen verliert, wechselt er vom laufenden Zustand in den Bereitschaftszustand. Warten Sie, bis das System erneut Ressourcen zuweist. Sie können die yield()-Methode auch in einem laufenden Thread aufrufen, wodurch CPU-Ressourcen freigegeben werden und die Methode wieder einsatzbereit ist.
Wenn die folgenden Situationen auftreten, wechselt der Thread vom laufenden Zustand in den blockierten Zustand:
① Der Thread ruft die Sleep-Methode auf, um das aktiv aufzugeben belegte Systemressourcen
② Der Thread ruft eine blockierende IO-Methode auf, bevor die Methode blockiert wird
③ Der Thread versucht, einen Synchronisationsmonitor zu erhalten wird von anderen Threads gehalten. Es gibt
④, der Thread wartet auf eine Benachrichtigung (nichtwenny)
⑤, das Programm ruft den Susp aufend-Methode des Threads, um den Thread-Hang zu beenden. Allerdings kann diese Methode leicht zu einem Deadlock führen, daher sollten Programme versuchen, die Verwendung dieser Methode zu vermeiden.
Wenn die run()-Methode des Threads ausgeführt oder gewaltsam beendet wird, tritt beispielsweise eine Ausnahme auf oder die Methoden stop(), desyory() usw. werden aufgerufen , es wird vom laufenden Zustand in den toten Zustand übergehen.
4. Blockierungsstatus
Ein Thread in einem laufenden Zustand unter bestimmten Umständen, z. B. beim Ausführen der Schlafmethode (Schlafmodus) oder beim Warten Ressourcen wie E/A-Geräte geben die CPU auf, unterbrechen vorübergehend ihren eigenen Betrieb und gehen in einen Blockierungszustand über.
Threads im blockierten Zustand können nicht in die Bereitschaftswarteschlange gelangen. Erst wenn die Ursache der Blockierung beseitigt ist, z. B. wenn die Ruhezeit abgelaufen ist oder das wartende E/A-Gerät im Leerlauf ist, wechselt der Thread in den Bereitschaftszustand, stellt sich wieder in die Bereitschaftswarteschlange und startet vom ursprünglichen Stopp Position nach Auswahl durch das System weiterlaufen.
5. Todeszustand
Wenn die run()-Methode des Threads ausgeführt oder zwangsweise beendet wird, gilt sie als tot. Dieses Thread-Objekt ist möglicherweise aktiv, aber es ist kein separat ausgeführter Thread mehr. Sobald ein Thread stirbt, kann er nicht wiederbelebt werden. Wenn die start()-Methode für einen toten Thread aufgerufen wird, wird eine java.lang.IllegalThreadStateException ausgelöst.
2. Kontrolle des Thread-Status
Java bietet einige praktische Methoden zur Kontrolle des Thread-Status.
.
Sie können sehen, dass viele Methoden als veraltet markiert wurden. Wir sollten sie so weit wie möglich vermeiden und uns auf start(), interrupt() und join() konzentrieren. , Direkte Steuerungsmethoden wie Sleep() und Yield() und indirekte Steuerungsmethoden wie setDaemon() und setPriority().
1. Thread-Ruhezustand - Ruhezustand
Wenn wir den aktuell ausgeführten Thread für einen bestimmten Zeitraum pausieren lassen und in einen Blockierungszustand wechseln müssen , dann Sie können die Schlafmethode von Thread aufrufen. Wie Sie oben sehen können, weist die Schlafmethode zwei Formen der Überladung auf, die Verwendungsmethode ist jedoch dieselbe.
Zum Beispiel möchten wir den Hauptthread alle 100 Millisekunden in den Ruhezustand versetzen und dann die Zahlen ausdrucken:
public class Test { public static void main(String[] args) throws InterruptedException { for(int i=;i<;i++){ System.out.println("main"+i); Thread.sleep(); } } }
Sie können das deutlich sehen gedruckte Zahlen Es gibt eine leichte Zeitlücke.
Achten Sie auf die folgenden Probleme
① Sleep ist eine statische Methode. Es ist am besten, sie nicht mit einem Instanzobjekt aufzurufen Thread, da er schläft, ist immer der aktuell laufende Thread, nicht das Thread-Objekt, das ihn aufruft. Er ist nur für das Thread-Objekt im laufenden Zustand gültig. Schauen Sie sich das folgende Beispiel an:
public class Test1 { public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()); MyThread myThread=new MyThread(); myThread.start(); myThread.sleep(1000);//这里sleep的就是main线程,而非myThread线程 Thread.sleep(10); for(int i=0;i<100;i++){ System.out.println("main"+i); } } }
② Die Java-Thread-Planung ist der Kern des Java-Multithreadings System und verbessern die Effizienz der Programmausführung. Unabhängig davon, wie der Programmierer den Zeitplan schreibt, kann er jedoch nur die Reihenfolge der Thread-Ausführung maximal beeinflussen, jedoch keine präzise Steuerung erreichen. Denn nach Verwendung der Schlafmethode wechselt der Thread erst nach Ablauf der Ruhezeit wieder in den Bereitschaftszustand. Der Übergang vom Bereitschaftszustand in den Betriebszustand wird vom System nicht gesteuert damit genau. Wenn also Thread.sleep(1000) aufgerufen wird, um den Thread für 1 Sekunde in den Ruhezustand zu versetzen, kann das Ergebnis länger als 1 Sekunde sein.
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(); } } } }
Sehen Sie sich die Ergebnisse einer bestimmten Operation an:
1 Thread-0 Thread wurde 0 Mal ausgeführt!
2. Thread-1 Thread wird 0 Mal ausgeführt!
3. Thread-1 Thread wird einmal ausgeführt!
4. Thread-0-Thread wird einmal ausgeführt!
5. Thread-0-Thread wird zweimal ausgeführt!
6. Thread-1 Thread wird zweimal ausgeführt!
Wie Sie sehen können, wird Thread 0 zuerst ausgeführt, dann wird Thread 1 einmal und dann noch einmal ausgeführt. Sie können sehen, dass die Ausführung nicht in der Schlafreihenfolge erfolgt.
2. Thread-Konzession - yield
Die yield()-Methode ähnelt in gewisser Weise der sleep()-Methode und ist auch eine Wird von der statischen Methode der Thread-Klasse bereitgestellt und kann auch den aktuell ausgeführten Thread anhalten und CPU-Ressourcen an andere Threads abgeben. Im Gegensatz zur Methode sleep () wechselt sie jedoch nicht in den Blockierungszustand, sondern in den Bereitschaftszustand. Die yield()-Methode hält einfach den aktuellen Thread an, ruft den bereiten Thread-Pool erneut auf und lässt ihn vom Thread-Scheduler des Systems erneut planen. Es ist durchaus möglich, dass diese Situation auftritt: Wenn ein Thread die yield()-Methode aufruft Der Thread-Scheduler plant es aus und wechselt zur Ausführung erneut in den Ausführungsstatus.
Wenn ein Thread tatsächlich die Methode yield() zum Anhalten aufruft, ist die Priorität dieselbe wie die des aktuellen Threads oder des Threads im Bereitschaftszustand mit einer höheren Priorität als der Der aktuelle Thread hat mehr Möglichkeiten zur Ausführung, was natürlich nur möglich ist, da wir den CPU-Planungsthread nicht genau stören können.
Verwendung von 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(); } } }
Der Unterschied zwischen der Sleep()-Methode und der yield()-Methode ist wie folgt:
① Nachdem die Schlafmethode den aktuellen Thread angehalten hat, wechselt er in den Blockierungszustand. Erst wenn die Ruhezeit abgelaufen ist, wechselt er in den Bereitschaftszustand. Nachdem die Yield-Methode aufgerufen wurde, wechselt sie direkt in den Bereitschaftszustand, sodass sie direkt nach dem Eintritt in den Bereitschaftszustand in den Ausführungszustand versetzt werden kann.
② Die Sleep-Methode deklariert, dass InterruptedException ausgelöst wird. Wenn Sie also die Sleep-Methode aufrufen, müssen Sie die Ausnahme abfangen oder explizit deklarieren, dass die Ausnahme ausgelöst wird. Die yield-Methode deklariert nicht, dass sie eine Aufgabenausnahme auslöst.
③ Die Sleep-Methode ist besser portierbar als die Yield-Methode. Verlassen Sie sich im Allgemeinen nicht auf die Yield-Methode, um die Ausführung gleichzeitiger Threads zu steuern.
3. Thread-Zusammenführung - Join
Die Bedeutung der Thread-Zusammenführung besteht darin, mehrere parallele Threads zur Ausführung in einem einzigen Thread zusammenzuführen Ein Szenario liegt vor, wenn ein Thread darauf warten muss, dass die Ausführung eines anderen Threads abgeschlossen ist, bevor er ausgeführt werden kann. Die Thread-Klasse stellt die Join-Methode bereit, um diese Funktion abzuschließen.
Wie Sie der Liste der Methoden oben entnehmen können, gibt es drei überladene Methoden:
void join()
Aktueller Thread usw. Es ist Zeit, dem Thread beizutreten und darauf zu warten, dass der Thread beendet wird.
void join(long millis)
当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
void join(long millis,int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); thread.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 + "次执行!"); } } }
在这个例子中,在主线程中调用thread.join(); 就是将主线程加入到thread子线程后面等待执行。不过有时间限制,为1毫秒。
4、线程的优先级
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~•0之间,也可以使用Thread类提供的三个静态常量:
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个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。
5、守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法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(); } } } }
执行结果:
1. 后台线程第0次执行!
2. 线程1第0次执行!
3. 线程1第1次执行!
4. 后台线程第1次执行!
5. 后台线程第2次执行!
6. 线程1第2次执行!
7. 线程1第3次执行!
8. 后台线程第3次执行!
9. 线程1第4次执行!
10. 后台线程第4次执行!
11. 后台线程第5次执行!
12. 后台线程第6次执行!
13. 后台线程第7次执行!
从上面的执行结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。
守护线程的用途:
守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。
6、如何结束一个线程
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法。
1、正常执行完run方法,然后结束掉
2、控制循环条件和判断条件的标识符来结束掉线程
比如说run方法这样写:
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); } } }
只要保证在一定的情况下,run方法能够执行完毕即可。而不是while(true)的无线循环。
3、使用interrupt结束一个线程。
诚然,使用第2中方法的标识符来结束一个线程,是一个不错的方法,但是如果,该线程是处于sleep、wait、join的状态的时候,while循环就不会执行,那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了。
可以使用interrupt这个巧妙的方式结束掉这个线程。
我们看看sleep、wait、join方法的声明:
public final void wait() throws InterruptedException public static native void sleep(long millis) throws InterruptedException public final void join() throws InterruptedException
可以看到,这三者有一个共同点,都抛出了一个InterruptedException的异常。
在什么时候会产生这样一个异常呢?
每个Thread都有一个中断状状态,默认为false。可以通过Thread对象的isInterrupted()方法来判断该线程的中断状态。可以通过Thread对象的interrupt()方法将中断状态设置为true。
当一个线程处于sleep、wait、join这三种状态之一的时候,如果此时他的中断状态为true,那么它就会抛出一个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. 1
2. false
3. 我马上去sleep了
4. 2
5. true
6. 我马上去sleep了
7. 异常捕获了false
可以看到,首先执行第一次while循环,在第一次循环中,睡眠2秒,然后将中断状态设置为true。当进入到第二次循环的时候,中断状态就是第一次设置的true,当它再次进入sleep的时候,马上就抛出了InterruptedException异常,然后被我们捕获了。然后中断状态又被重新自动设置为false了(从最后一条输出可以看出来)。
所以,我们可以使用interrupt方法结束一个线程。具体使用如下:
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
4. 中断异常被捕获了
或者
0
1
2
3
5. 中断异常被捕获了
这两种结果恰恰说明了 只要一个线程的中断状态一旦为true,只要它进入sleep等状态,或者处于sleep状态,立马回抛出InterruptedException异常。
第一种情况,是当主线程从3秒睡眠状态醒来之后,调用了子线程的interrupt方法,此时子线程正处于sleep状态,立马抛出InterruptedException异常。
Die erste Situation ist, wenn der Hauptthread aus dem 3-Sekunden-Ruhezustand aufwacht und die Interrupt-Methode des Unterthreads aufruft. Zu diesem Zeitpunkt befindet sich der Unterthread noch nicht im Ruhezustand. Während der dritten While-Schleife wechselt es dann in den Ruhezustand und löst sofort eine InterruptedException aus.
Das obige ist der detaillierte Inhalt vonDetaillierte grafische Erläuterung des Lebenszyklus und der Statuskontrolle in Java-Threads. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!