Heim  >  Artikel  >  Java  >  Umfassende Analyse des Java-Thread-Multithreadings

Umfassende Analyse des Java-Thread-Multithreadings

高洛峰
高洛峰Original
2017-01-05 15:27:441456Durchsuche

Multithreading ist ein sehr wichtiger Wissenspunkt in Java. Ich hoffe, dass Sie es beherrschen.

1. Thread-Lebenszyklus und fünf Grundzustände

Schauen Sie sich zum Lebenszyklus von Threads in Java zunächst das klassischere Bild unten an:

Java Thread多线程全面解析

Das obige Bild deckt im Wesentlichen die wichtigen Wissenspunkte von Multithreading in Java ab. Nachdem Sie jeden Wissenspunkt in der obigen Abbildung gemeistert haben, können Sie Multithreading in Java grundsätzlich beherrschen. Dazu gehören hauptsächlich:

Java-Thread hat fünf Grundzustände

Neuer Zustand (Neu): Wenn das Thread-Objektpaar erstellt wird, wechselt es in den neuen Zustand, wie zum Beispiel: Thread t = new MyThread();

Bereitschaftszustand (ausführbar): Wenn die start()-Methode (t.start();) des Thread-Objekts aufgerufen wird, wechselt der Thread in den Bereitschaftszustand . Der Thread im Bereitschaftszustand bedeutet nur, dass der Thread jederzeit bereit ist und auf die Ausführung der CPU-Planung wartet. Dies bedeutet nicht, dass der Thread sofort nach der Ausführung von t.start() ausgeführt wird > Ausführungsstatus (Running): Wenn die CPU beginnt, den Thread im Bereitschaftszustand zu planen, kann der Thread zu diesem Zeitpunkt tatsächlich ausgeführt werden, dh er wechselt in den Ausführungszustand. Hinweis: Der Bereitschaftszustand ist der einzige Zugang zum Ausführungszustand. Das heißt, wenn ein Thread zur Ausführung in den Ausführungszustand wechseln möchte, muss er sich zunächst im Bereitschaftszustand befinden : Im laufenden Zustand gibt der Thread im Thread vorübergehend das Recht auf, die CPU zu verwenden, und wechselt zu diesem Zeitpunkt in den Blockierungszustand, bis er in den Bereitschaftszustand wechselt Wird von der CPU erneut aufgerufen, um in den Betriebszustand zu gelangen.

Je nach unterschiedlichen Gründen für die Blockierung kann der Blockierungsstatus in drei Typen unterteilt werden:


1. Warten auf Blockierung: Der Thread im laufenden Zustand führt die Methode wait() aus um diesen Thread in den wartenden Blockierungszustand zu versetzen;

2. Wenn der Thread die synchronisierte Synchronisationssperre nicht erhalten kann (weil die Sperre von anderen Threads belegt ist), wird er in den Status „Warteblockierung“ versetzt synchronisierter Blockierungsstatus;

3. Andere Blockierung – Wenn der Thread Sleep() oder Join() aufgerufen oder eine E/A-Anfrage ausgegeben wird, wechselt der Thread in den Blockierungsstatus. Wenn der Zustand „sleep()“ abläuft, wartet „join()“ auf die Beendigung oder Zeitüberschreitung des Threads oder auf den Abschluss der E/A-Verarbeitung, und der Thread kehrt in den Bereitschaftszustand zurück.

Toter Zustand (Dead): Der Thread hat die Ausführung abgeschlossen oder die run()-Methode aufgrund einer Ausnahme verlassen und der Thread beendet seinen Lebenszyklus.

2. Erstellung und Start von Java-Multi-Threads

Die Erstellung von Threads in Java ist in drei Grundformen üblich

1 . Vererbungs-Thread-Klasse, überschreiben Sie die run()-Methode dieser Klasse.

Erben Sie wie oben gezeigt die Thread-Klasse und definieren Sie eine neue Thread-Klasse MyThread, indem Sie die run()-Methode überschreiben. Der Methodenkörper der run()-Methode stellt die Aufgaben dar, die der Thread ausführen muss , genannt Es ist der Thread-Ausführungskörper. Wenn dieses Thread-Klassenobjekt erstellt wird, wird ein neuer Thread erstellt und tritt in den neuen Thread-Status ein. Durch Aufrufen der start()-Methode, auf die das Thread-Objekt verweist, wechselt der Thread in den Bereitschaftszustand. Zu diesem Zeitpunkt wird der Thread je nach CPU-Planungszeitpunkt möglicherweise nicht sofort ausgeführt.

2. Implementieren Sie die Runnable-Schnittstelle und schreiben Sie die run()-Methode neu. Erstellen Sie eine Instanz der Runnable-Implementierungsklasse als Thread Das Ziel der Klasse wird zum Erstellen eines Thread-Objekts verwendet, das das eigentliche Thread-Objekt ist.

class MyThread extends Thread {
private int i = ;
@Override
public void run() {
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
} 
public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Thread myThread = new MyThread(); // 创建一个新的线程 myThread 此线程进入新建状态
Thread myThread = new MyThread(); // 创建一个新的线程 myThread 此线程进入新建状态
myThread.start(); // 调用start()方法使得线程进入就绪状态
myThread.start(); // 调用start()方法使得线程进入就绪状态
}
}
}
}

Ich glaube, dass jeder mit den beiden oben genannten Möglichkeiten zum Erstellen neuer Threads vertraut ist. Welche Beziehung besteht also zwischen Thread und Runnable? Schauen wir uns zunächst das folgende Beispiel an.

In ähnlicher Weise ist die Methode zum Erstellen eines Threads durch Implementierung der Runnable-Schnittstelle ähnlich, aber der Unterschied besteht darin:

class MyRunnable implements Runnable {
private int i = ;
@Override
public void run() {
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
} 
public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
Thread thread = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
Thread thread = new Thread(myRunnable);
thread.start(); // 调用start()方法使得线程进入就绪状态
thread.start();
}
}
}
}

Kann diese Methode reibungslos einen neuen Thread erstellen? Die Antwort ist ja. Was den Thread-Ausführungskörper betrifft, handelt es sich derzeit um die run()-Methode in der MyRunnable-Schnittstelle oder die run()-Methode in der MyThread-Klasse? Durch die Ausgabe wissen wir, dass der Thread-Ausführungskörper die run()-Methode in der MyThread-Klasse ist. Tatsächlich ist der Grund sehr einfach, da die Thread-Klasse selbst auch die Runnable-Schnittstelle implementiert und die run()-Methode zuerst in der Runnable-Schnittstelle definiert wurde.

public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Runnable myRunnable = new MyRunnable();
Thread thread = new MyThread(myRunnable);
thread.start();
}
}
}
}
class MyRunnable implements Runnable {
private int i = ;
@Override
public void run() {
System.out.println("in MyRunnable run");
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class MyThread extends Thread {
private int i = ;
public MyThread(Runnable runnable){
super(runnable);
}
@Override
public void run() {
System.out.println("in MyThread run");
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}

Werfen wir einen Blick auf die Implementierung der run()-Methode in der Runnable-Schnittstelle in der Thread-Klasse:

Thread thread = new MyThread(myRunnable);

Mit anderen Worten, wenn die run()-Methode in der Thread-Klasse wird ausgeführt Wenn das Ziel existiert, wird zunächst beurteilt, ob das Ziel existiert, und wenn es existiert, wird die run()-Methode im Ziel ausgeführt, d. h. die run()-Methode in der Klasse implementiert die Runnable-Schnittstelle und überschreibt die run()-Methode. Im oben angegebenen Beispiel wird jedoch aufgrund des Vorhandenseins von Polymorphismus die run()-Methode in der Thread-Klasse überhaupt nicht ausgeführt, die run()-Methode im Laufzeittyp, also der MyThread-Klasse, jedoch direkt zuerst ausgeführt.

public interface Runnable {
public abstract void run();
}

3. Verwenden Sie Callable- und Future-Schnittstellen, um Threads zu erstellen. Erstellen Sie insbesondere eine Implementierungsklasse der Callable-Schnittstelle und implementieren Sie die Methode clam(). Verwenden Sie die FutureTask-Klasse, um das Objekt der Callable-Implementierungsklasse zu umschließen, und verwenden Sie dieses FutureTask-Objekt als Ziel des Thread-Objekts, um einen Thread zu erstellen.

  @Override
public void run() {
if (target != null) {
target.run();
}
}

Es scheint etwas kompliziert zu sein, aber es wird klar, wenn man sich nur ein Beispiel ansieht.

public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = ;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = ;
for (; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}

首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。那么看下FutureTask类的定义:

public class FutureTask<V> implements RunnableFuture<V> {
//....
} 
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}

于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。

执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢?

原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。

上述主要讲解了三种常见的线程创建方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法。

三. Java多线程的就绪、运行和死亡状态

就绪状态转换为运行状态:当此线程得到处理器资源;

运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:

public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
thread.start();
}
if(i == ){
myRunnable.stopThread();
}
}
}
}
class MyRunnable implements Runnable {
private boolean stop;
@Override
public void run() {
for (int i = ; i < && !stop; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public void stopThread() {
this.stop = true;
}
}

以上所述是小编给大家介绍的Java Thread多线程全面解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对PHP中文网的支持!

更多Java Thread多线程全面解析相关文章请关注PHP中文网!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn