Thread-Verhalten in der JVM

Threading bezieht sich auf die Praxis, Programmierprozesse gleichzeitig auszuführen, um die Anwendungsleistung zu verbessern. Während es nicht so üblich ist, mit Threads direkt in Geschäftsanwendungen zu arbeiten, werden sie in Java-Frameworks ständig verwendet.

Beispielsweise verwenden Frameworks, die ein großes Informationsvolumen verarbeiten, wie z. B. Spring Batch, Threads zum Verwalten von Daten. Das gleichzeitige Bearbeiten von Threads oder CPU-Prozessen verbessert die Leistung und führt zu schnelleren und effizienteren Programmen.

Holen Sie sich den Quellcode

Holen Sie sich den Code für diesen Java Challenger. Sie können Ihre eigenen Tests ausführen, während Sie den Beispielen folgen.

Finden Sie Ihren ersten Thread: Javas main () -Methode

Auch wenn Sie noch nie direkt mit Java-Threads gearbeitet haben, haben Sie indirekt mit ihnen gearbeitet, da Javas main () -Methode einen Haupt-Thread enthält. Jedes Mal, wenn Sie die main()Methode ausgeführt haben, haben Sie auch die Hauptmethode ausgeführt Thread.

Das Studium der ThreadKlasse ist sehr hilfreich, um zu verstehen, wie Threading in Java-Programmen funktioniert. Wir können auf den Thread zugreifen, der ausgeführt wird, indem wir die currentThread().getName()Methode aufrufen , wie hier gezeigt:

 public class MainThread { public static void main(String... mainThread) { System.out.println(Thread.currentThread().getName()); } } 

Dieser Code gibt "main" aus und identifiziert den aktuell ausgeführten Thread. Zu wissen, wie der ausgeführte Thread identifiziert wird, ist der erste Schritt, um Thread-Konzepte zu absorbieren.

Der Java-Thread-Lebenszyklus

Bei der Arbeit mit Threads ist es wichtig, den Thread-Status zu kennen. Der Java-Thread-Lebenszyklus besteht aus sechs Thread-Zuständen:

  • Neu : Ein neues Thread()wurde instanziiert.
  • Runnable : Die Methode von Thread' start()wurde aufgerufen.
  • Wird ausgeführt : Die start()Methode wurde aufgerufen und der Thread wird ausgeführt.
  • Angehalten : Der Thread wird vorübergehend angehalten und kann von einem anderen Thread fortgesetzt werden.
  • Blockiert : Der Thread wartet auf eine Gelegenheit zum Ausführen. Dies geschieht, wenn ein Thread die synchronized()Methode bereits aufgerufen hat und der nächste Thread warten muss, bis er fertig ist.
  • Beendet : Die Ausführung des Threads ist abgeschlossen.
Rafael Chinelato Del Nero

Es gibt mehr zu erforschen und zu verstehen über Thread-Zustände, aber die Informationen in Abbildung 1 reichen aus, um diese Java-Herausforderung zu lösen.

Gleichzeitige Verarbeitung: Erweitern einer Thread-Klasse

Im einfachsten Fall erfolgt die gleichzeitige Verarbeitung durch Erweitern einer ThreadKlasse, wie unten gezeigt.

 public class InheritingThread extends Thread { InheritingThread(String threadName) { super(threadName); } public static void main(String... inheriting) { System.out.println(Thread.currentThread().getName() + " is running"); new InheritingThread("inheritingThread").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } } 

Hier laufen zwei Threads: der MainThreadund der InheritingThread. Wenn wir die start()Methode mit der neuen aufrufen inheritingThread(), wird die Logik in der run()Methode ausgeführt.

Wir übergeben auch den Namen des zweiten Threads im ThreadKlassenkonstruktor, sodass die Ausgabe wie folgt lautet:

 main is running. inheritingThread is running. 

Die Runnable-Schnittstelle

Anstatt die Vererbung zu verwenden, können Sie die Runnable-Schnittstelle implementieren. Das Übergeben Runnableinnerhalb eines ThreadKonstruktors führt zu weniger Kopplung und mehr Flexibilität. Nach dem Bestehen Runnablekönnen wir die start()Methode genau wie im vorherigen Beispiel aufrufen :

 public class RunnableThread implements Runnable { public static void main(String... runnableThread) { System.out.println(Thread.currentThread().getName()); new Thread(new RunnableThread()).start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } } 

Nicht-Daemon vs Daemon-Threads

In Bezug auf die Ausführung gibt es zwei Arten von Threads:

  • Nicht-Daemon-Threads werden bis zum Ende ausgeführt. Der Hauptthread ist ein gutes Beispiel für einen Nicht-Daemon-Thread. Code in main()wird immer bis zum Ende ausgeführt, es sei denn, a System.exit()zwingt das Programm zum Abschluss.
  • Ein Daemon-Thread ist das Gegenteil, im Grunde ein Prozess, der erst am Ende ausgeführt werden muss.

Beachten Sie die Regel : Wenn ein umschließender Nicht-Daemon-Thread vor einem Daemon-Thread endet, wird der Daemon-Thread erst am Ende ausgeführt.

Sehen Sie sich dieses Beispiel an, um die Beziehung zwischen Daemon- und Nicht-Daemon-Threads besser zu verstehen:

 import java.util.stream.IntStream; public class NonDaemonAndDaemonThread { public static void main(String... nonDaemonAndDaemon) throws InterruptedException { System.out.println("Starting the execution in the Thread " + Thread.currentThread().getName()); Thread daemonThread = new Thread(() -> IntStream.rangeClosed(1, 100000) .forEach(System.out::println)); daemonThread.setDaemon(true); daemonThread.start(); Thread.sleep(10); System.out.println("End of the execution in the Thread " + Thread.currentThread().getName()); } } 

In diesem Beispiel habe ich einen Daemon-Thread verwendet, um einen Bereich von 1 bis 100.000 zu deklarieren, alle zu iterieren und dann zu drucken. Denken Sie jedoch daran, dass ein Daemon-Thread die Ausführung nicht abschließt, wenn der Haupt-Thread des Nicht-Daemons zuerst beendet wird.

Die Ausgabe erfolgt wie folgt:

  1. Start der Ausführung im Hauptthread.
  2. Drucken Sie Zahlen von 1 bis möglicherweise 100.000.
  3. Ende der Ausführung im Hauptthread, sehr wahrscheinlich bevor die Iteration auf 100.000 abgeschlossen ist.

Die endgültige Ausgabe hängt von Ihrer JVM-Implementierung ab.

Und das bringt mich zu meinem nächsten Punkt: Threads sind unvorhersehbar.

Thread-Priorität und die JVM

Es ist möglich, die Thread-Ausführung mit der setPriorityMethode zu priorisieren , aber wie sie behandelt wird, hängt von der JVM-Implementierung ab. Linux, MacOS und Windows haben unterschiedliche JVM-Implementierungen, und jede behandelt die Thread-Priorität gemäß ihren eigenen Standardeinstellungen.

Die von Ihnen festgelegte Thread-Priorität beeinflusst jedoch die Reihenfolge des Thread-Aufrufs. Die drei in der ThreadKlasse deklarierten Konstanten sind:

 /** * The minimum priority that a thread can have. */ public static final int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public static final int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public static final int MAX_PRIORITY = 10; 

Try running some tests on the following code to see what execution priority you end up with:

 public class ThreadPriority { public static void main(String... threadPriority) { Thread moeThread = new Thread(() -> System.out.println("Moe")); Thread barneyThread = new Thread(() -> System.out.println("Barney")); Thread homerThread = new Thread(() -> System.out.println("Homer")); moeThread.setPriority(Thread.MAX_PRIORITY); barneyThread.setPriority(Thread.NORM_PRIORITY); homerThread.setPriority(Thread.MIN_PRIORITY); homerThread.start(); barneyThread.start(); moeThread.start(); } } 

Even if we set moeThread as MAX_PRIORITY, we cannot count on this thread being executed first. Instead, the order of execution will be random.

Constants vs enums

The Thread class was introduced with Java 1.0. At that time, priorities were set using constants, not enums. There's a problem with using constants, however: if we pass a priority number that is not in the range of 1 to 10, the setPriority() method will throw an IllegalArgumentException. Today, we can use enums to get around this issue. Using enums makes it impossible to pass an illegal argument, which both simplifies the code and gives us more control over its execution.

Take the Java threads challenge!

You've learned just a little bit about threads, but it's enough for this post's Java challenge.

To start, study the following code:

 public class ThreadChallenge { private static int wolverineAdrenaline = 10; public static void main(String... doYourBest) { new Motorcycle("Harley Davidson").start(); Motorcycle fastBike = new Motorcycle("Dodge Tomahawk"); fastBike.setPriority(Thread.MAX_PRIORITY); fastBike.setDaemon(false); fastBike.start(); Motorcycle yamaha = new Motorcycle("Yamaha YZF"); yamaha.setPriority(Thread.MIN_PRIORITY); yamaha.start(); } static class Motorcycle extends Thread { Motorcycle(String bikeName) { super(bikeName); } @Override public void run() { wolverineAdrenaline++; if (wolverineAdrenaline == 13) { System.out.println(this.getName()); } } } } 

What will be the output of this code? Analyze the code and try to determine the answer for yourself, based on what you've learned.

A. Harley Davidson

B. Dodge Tomahawk

C. Yamaha YZF

D. Indeterminate

What just happened? Understanding threads behavior

In the above code, we created three threads. The first thread is Harley Davidson, and we assigned this thread the default priority. The second thread is Dodge Tomahawk, assigned MAX_PRIORITY. The third is Yamaha YZF, with MIN_PRIORITY. Then we started the threads.

In order to determine the order the threads will run in, you might first note that the Motorcycle class extends the Thread class, and that we've passed the thread name in the constructor. We've also overridden the run() method with a condition: if wolverineAdrenaline is equals to 13.

Even though Yamaha YZF is the third thread in our order of execution, and has MIN_PRIORITY, there's no guarantee that it will be executed last for all JVM implementations.

You might also note that in this example we set the Dodge Tomahawk thread as daemon. Because it's a daemon thread, Dodge Tomahawk may never complete execution. But the other two threads are non-daemon by default, so the Harley Davidson and Yamaha YZF threads will definitely complete their execution.

To conclude, the result will be D: Indeterminate, because there is no guarantee that the thread scheduler will follow our order of execution or thread priority.

Remember, we can't rely on program logic (order of threads or thread priority) to predict the JVM's order of execution.

Video challenge! Debugging variable arguments

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the thread behavior challenge:

Common mistakes with Java threads

  • Invoking the run() method to try to start a new thread.
  • Trying to start a thread twice (this will cause an IllegalThreadStateException).
  • Allowing multiple processes to change the state of an object when it shouldn't change.
  • Writing program logic that relies on thread priority (you can't predict it).
  • Relying on the order of thread execution--even if we start a thread first, there is no guarantee it will be executed first.

What to remember about Java threads

  • Invoke the start() method to start a Thread.
  • It's possible to extend the Thread class directly in order to use threads.
  • It's possible to implement a thread action inside a Runnable interface.
  • Thread priority depends on the JVM implementation.
  • Thread behavior will always depend on the JVM implementation.
  • A daemon thread won't complete if an enclosing non-daemon thread ends first.

Learn more about Java threads on JavaWorld

  • Read the Java 101 threads series to learn more about threads and runnables, thread synchronization, thread scheduling with wait/notify, and thread death.
  • Modern threading: A Java concurrency primer introduces java.util.concurrent and answers common questions for developers new to Java concurrency.
  • Modern threading for not-quite-beginners offers more advanced tips and best practices for working with java.util.concurrent.

More from Rafael

  • Get more quick code tips: Read all the posts in the Java Challengers series.
  • Build your Java skills: Visit the Java Dev Gym for a code workout.
  • Want to work on stress free projects and write bug-free code? Visit the NoBugsProject for your copy of No Bugs, No Stress - Create a Life-Changing Software Without Destroying Your Life.

Diese Geschichte "Thread-Verhalten in der JVM" wurde ursprünglich von JavaWorld veröffentlicht.