Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung von Multithreading basierend auf einer Java-Rezension

Detaillierte Erläuterung von Multithreading basierend auf einer Java-Rezension

黄舟
黄舟Original
2016-12-19 14:50:431171Durchsuche

Thread ist die Grundeinheit des Betriebssystembetriebs. Er ist in einem Prozess gekapselt. Auch wenn wir keinen Thread manuell erstellen, wird im Prozess ein Standardthread ausgeführt.

Wenn wir für die JVM ein Single-Thread-Programm zur Ausführung schreiben, werden in der JVM mindestens zwei Threads ausgeführt, einer ist das von uns erstellte Programm und der andere ist die Speicherbereinigung.

Thread-Grundinformationen

Über die Methode Thread.currentThread() können wir einige Informationen über den aktuellen Thread abrufen und ändern.

Sehen wir uns den folgenden Code an:

查看并修改当前线程的属性
 String name = Thread.currentThread().getName();
         int priority = Thread.currentThread().getPriority();
         String groupName = Thread.currentThread().getThreadGroup().getName();
         boolean isDaemon = Thread.currentThread().isDaemon();
         System.out.println("Thread Name:" + name);
         System.out.println("Priority:" + priority);
         System.out.println("Group Name:" + groupName);
         System.out.println("IsDaemon:" + isDaemon);

         Thread.currentThread().setName("Test");
         Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
         name = Thread.currentThread().getName();
         priority = Thread.currentThread().getPriority();
         groupName = Thread.currentThread().getThreadGroup().getName();
         isDaemon = Thread.currentThread().isDaemon();
         System.out.println("Thread Name:" + name);
         System.out.println("Priority:" + priority);

Die aufgeführten Attribute lauten wie folgt:


Gruppenname, jeder Thread wird von Standardmäßig befindet es sich in einer Thread-Gruppe. Eine Thread-Gruppe kann auch Unter-Thread-Gruppen enthalten, sodass Threads und Thread-Gruppen eine Baumstruktur bilden.

Name, jeder Thread hat einen Namen. Wenn nicht explizit angegeben, lautet die Namensregel „Thread-xxx“.

Priorität, jeder Thread hat seine eigene Priorität, und die Art und Weise, wie die JVM mit der Priorität umgeht, ist „präventiv“. Wenn die JVM einen Thread mit hoher Priorität findet, führt sie den Thread sofort aus. Bei mehreren Threads mit gleicher Priorität fragt die JVM diese ab. Die Thread-Priorität von Java reicht von 1 bis 10, wobei der Standardwert 5 ist. Die Thread-Klasse definiert zwei Konstanten: MIN_PRIORITY und MAX_PRIORITY, um die höchste und niedrigste Priorität darzustellen.

Wir können uns den folgenden Code ansehen, der zwei Threads mit unterschiedlichen Prioritäten definiert:

线程优先级示例
 public static void priorityTest()
 {
     Thread thread1 = new Thread("low")
     {
         public void run()
         {
             for (int i = 0; i < 5; i++)
             {
                 System.out.println("Thread 1 is running.");
             }
         }
     };

     Thread thread2 = new Thread("high")
     {
         public void run()
         {
             for (int i = 0; i < 5; i++)
             {
                 System.out.println("Thread 2 is running.");
             }
         }
     };

     thread1.setPriority(Thread.MIN_PRIORITY);
     thread2.setPriority(Thread.MAX_PRIORITY);
     thread1.start();
     thread2.start();
 }

Wie aus den laufenden Ergebnissen ersichtlich ist, wird nach Abschluss der Ausführung des Threads mit hoher Priorität der Thread mit niedriger Priorität Es werden nur Threads mit Priorität ausgeführt.
isDaemon, dieses Attribut wird verwendet, um die Beziehung zwischen übergeordneten und untergeordneten Threads zu steuern. Wenn der übergeordnete Thread endet, enden auch alle untergeordneten Threads. Im Gegenteil, der Lebenszyklus des untergeordneten Threads wird vom übergeordneten Thread nicht beeinflusst.
Sehen wir uns das folgende Beispiel an:

IsDaemon 示例
 public static void daemonTest()
 {
     Thread thread1 = new Thread("daemon")
     {
         public void run()
         {
             Thread subThread = new Thread("sub")
             {
                 public void run()
                 {
                     for(int i = 0; i < 100; i++)
                     {
                         System.out.println("Sub Thread Running " + i);
                     }
                 }
             };
             subThread.setDaemon(true);
             subThread.start();
             System.out.println("Main Thread end.");
         }
     };

     thread1.start();
 }

Wenn wir die laufenden Ergebnisse des obigen Codes mit denen nach dem Löschen von subThread.setDaemon(true); vergleichen, können wir feststellen, dass der Sub-Thread dies tun wird Vollständige Ausführung während des letzteren Vorgangs. Dann endet er, und im ersteren endet der Unterthread bald.


So erstellen Sie einen Thread

Im obigen Inhalt geht es darum, einige Informationen im Standardthread zu demonstrieren. Wie sollte man also einen Thread erstellen? In Java gibt es drei Möglichkeiten, Threads zu erstellen.

Threads in Java erben entweder die Thread-Klasse oder implementieren die Runnable-Schnittstelle. Gehen wir sie einzeln durch.

Verwenden Sie innere Klassen, um Threads zu erstellen

Wir können innere Klassen verwenden, um Threads zu erstellen. Der Prozess besteht darin, eine Variable vom Typ Thread zu deklarieren und die Ausführungsmethode zu überschreiben. Der Beispielcode lautet wie folgt:

使用内部类创建线程
 public static void createThreadByNestClass()
 {
     Thread thread = new Thread()
     {
         public void run()
         {
             for (int i =0; i < 5; i++)
             {
                 System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
             }
             System.out.println("Thread " + Thread.currentThread().getName() + " is finished.");
         }
     };
     thread.start();
 }

Thread erben, um einen Thread zu erstellen


Wir können eine Klasse von Thread ableiten und ihre Ausführungsmethode überschreiben. so Die Methode ist ähnlich wie oben. Der Beispielcode lautet wie folgt:

派生Thread类以创建线程
 class MyThread extends Thread
 {
     public void run()
     {
         for (int i =0; i < 5; i++)
         {
             System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
         }
         System.out.println("Thread " + Thread.currentThread().getName() + " is finished.");
     }
 }

 
 public static void createThreadBySubClass()
 {
     MyThread thread = new MyThread();
     thread.start();
 }

Implementieren Sie die Runnable-Schnittstelle, um Threads zu erstellen


Wir können eine Klasse so definieren, dass sie die Runnable-Schnittstelle implementiert , und fügen Sie dann die Klasse ein. Die Instanz wird als Parameter zum Erstellen des Thread-Variablenkonstruktors verwendet. Der Beispielcode lautet wie folgt:

实现Runnable接口以创建线程
 class MyRunnable implements Runnable
 {
     public void run() 
     {
         for (int i =0; i < 5; i++)
         {
             System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
         }
         System.out.println("Thread " + Thread.currentThread().getName() + " is finished.");
     }
 }

 
 public static void createThreadByRunnable()
 {
     MyRunnable runnable = new MyRunnable();
     Thread thread = new Thread(runnable);
     thread.start();
 }

Threads können mit den oben genannten drei Methoden erstellt werden. Aus dem Beispielcode geht hervor, dass die von Threads ausgeführten Funktionen dieselben sind. Was sind also die Unterschiede zwischen diesen drei Erstellungsmethoden? Methoden?


Dies betrifft den Ausführungsmodus des Multithreadings in Java. Für Java gibt es beim Ausführen von Multithreading „Multi-Object-Multithreading“ und „Single“. -Objekt-Multithreading“ „Der Unterschied:

Multiobjekt-Multithreading, das Programm erstellt während der Ausführung mehrere Thread-Objekte und ein Thread wird für jedes Objekt ausgeführt.
Einzelobjekt-Multithreading: Das Programm erstellt während der Ausführung ein Thread-Objekt und führt darauf mehrere Threads aus.

Aus Sicht der Thread-Synchronisierung und -Planung ist Multi-Objekt-Multi-Threading natürlich einfacher. Von den oben genannten drei Thread-Erstellungsmethoden sind die ersten beiden „Multi-Objekt-Multi-Thread“ und die dritte kann entweder „Multi-Objekt-Multi-Thread“ oder „Einzel-Objekt-Single-Thread“ verwenden.

Sehen wir uns den folgenden Beispielcode an, der die Object.notify-Methode verwendet, um einen Thread für das Objekt zu aktivieren, und die Object.notifyAll-Methode aktiviert alle Threads für das Objekt .

notify示例
 public class NotifySample {

     public static void main(String[] args) throws InterruptedException
     {
         notifyTest();
         notifyTest2();
         notifyTest3();
     }

     private static void notifyTest() throws InterruptedException
     {
         MyThread[] arrThreads = new MyThread[3];
         for (int i = 0; i < arrThreads.length; i++)
         {
             arrThreads[i] = new MyThread();
             arrThreads[i].id = i;
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         Thread.sleep(500);
         for (int i = 0; i < arrThreads.length; i++)
         {
             synchronized(arrThreads[i])
             {
                 arrThreads[i].notify();
             }
         }
     }

     private static void notifyTest2() throws InterruptedException
     {
         MyRunner[] arrMyRunners = new MyRunner[3];
         Thread[] arrThreads = new Thread[3];
         for (int i = 0; i < arrThreads.length; i++)
         {
             arrMyRunners[i] = new MyRunner();
             arrMyRunners[i].id = i;
             arrThreads[i] = new Thread(arrMyRunners[i]);
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         Thread.sleep(500);
         for (int i = 0; i < arrMyRunners.length; i++)
         {
             synchronized(arrMyRunners[i])
             {
                 arrMyRunners[i].notify();
             }
         }
     }

     private static void notifyTest3() throws InterruptedException
     {
         MyRunner runner = new MyRunner();
         Thread[] arrThreads = new Thread[3];
         for (int i = 0; i < arrThreads.length; i++)
         {
             arrThreads[i] = new Thread(runner);
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         Thread.sleep(500);

         synchronized(runner)
         {
             runner.notifyAll();
         }
     }
 }

 class MyThread extends Thread
 {
     public int id = 0;
     public void run()
     {
         System.out.println("第" + id + "个线程准备休眠5分钟。");
         try
         {
             synchronized(this)
             {
                 this.wait(5*60*1000);
             }
         }
         catch(InterruptedException ex)
         {
             ex.printStackTrace();
         }
         System.out.println("第" + id + "个线程被唤醒。");
     }
 }

 class MyRunner implements Runnable
 {
     public int id = 0;
     public void run() 
     {
         System.out.println("第" + id + "个线程准备休眠5分钟。");
         try
         {
             synchronized(this)
             {
                 this.wait(5*60*1000);
             }
         }
         catch(InterruptedException ex)
         {
             ex.printStackTrace();
         }
         System.out.println("第" + id + "个线程被唤醒。");
     }

 }

Im Beispielcode sind notifyTest() und notifyTest2() „Multi-Object-Multi-Threading“. Obwohl der Thread in notifyTest2() die Runnable-Schnittstelle implementiert, wenn er das Thread-Array darin definiert , jedes Element verwendet eine neue ausführbare Instanz. notifyTest3() gehört zum „Einzelobjekt-Multithreading“, da wir nur eine ausführbare Instanz definieren und alle Threads diese Instanz verwenden.


Die notifyAll-Methode eignet sich für das Szenario „Einzelobjekt-Multithread“, da die notify-Methode nur einen Thread für das Objekt zufällig aktiviert.

Thread-Statuswechsel

Für einen Thread kann der Status des Threads vom Zeitpunkt seiner Erstellung bis zum Ende der Thread-Ausführung während dieses Vorgangs wie folgt aussehen:

    创建:已经有Thread实例了, 但是CPU还有为其分配资源和时间片。
    就绪:线程已经获得了运行所需的所有资源,只等CPU进行时间调度。
    运行:线程位于当前CPU时间片中,正在执行相关逻辑。
    休眠:一般是调用Thread.sleep后的状态,这时线程依然持有运行所需的各种资源,但是不会被CPU调度。
    挂起:一般是调用Thread.suspend后的状态,和休眠类似,CPU不会调度该线程,不同的是,这种状态下,线程会释放所有资源。
    死亡:线程运行结束或者调用了Thread.stop方法。

下面我们来演示如何进行线程状态切换,首先我们会用到下面方法:

    Thread()或者Thread(Runnable):构造线程。
    Thread.start:启动线程。
    Thread.sleep:将线程切换至休眠状态。
    Thread.interrupt:中断线程的执行。
    Thread.join:等待某线程结束。
    Thread.yield:剥夺线程在CPU上的执行时间片,等待下一次调度。
    Object.wait:将Object上所有线程锁定,直到notify方法才继续运行。
    Object.notify:随机唤醒Object上的1个线程。
    Object.notifyAll:唤醒Object上的所有线程。

下面,就是演示时间啦!!!

线程等待与唤醒

这里主要使用Object.wait和Object.notify方法,请参见上面的notify实例。需要注意的是,wait和notify都必须针对同一个对象,当我们使用实现Runnable接口的方式来创建线程时,应该是在Runnable对象而非Thread对象上使用这两个方法。

线程的休眠与唤醒

Thread.sleep实例
 public class SleepSample {

     public static void main(String[] args) throws InterruptedException
     {
         sleepTest();
     }

     private static void sleepTest() throws InterruptedException
     {
         Thread thread = new Thread()
         {
             public void run()
             {
                 System.out.println("线程 " + Thread.currentThread().getName() + "将要休眠5分钟。");
                 try
                 {
                     Thread.sleep(5*60*1000);
                 }
                 catch(InterruptedException ex)
                 {
                     System.out.println("线程 " + Thread.currentThread().getName() + "休眠被中断。");
                 }
                 System.out.println("线程 " + Thread.currentThread().getName() + "休眠结束。");
             }
         };
         thread.setDaemon(true);
         thread.start();
         Thread.sleep(500);
         thread.interrupt();
     }

 }

线程在休眠过程中,我们可以使用Thread.interrupt将其唤醒,这时线程会抛出InterruptedException。

线程的终止


虽然有Thread.stop方法,但该方法是不被推荐使用的,我们可以利用上面休眠与唤醒的机制,让线程在处理IterruptedException时,结束线程。

Thread.interrupt示例
 public class StopThreadSample {

     public static void main(String[] args) throws InterruptedException
     {
         stopTest();
     }

     private static void stopTest() throws InterruptedException
     {
         Thread thread = new Thread()
         {
             public void run()
             {
                 System.out.println("线程运行中。");
                 try
                 {
                     Thread.sleep(1*60*1000);
                 }
                 catch(InterruptedException ex)
                 {
                     System.out.println("线程中断,结束线程");
                     return;
                 }
                 System.out.println("线程正常结束。");
             }
         };
         thread.start();
         Thread.sleep(500);
         thread.interrupt();
     }
 }

线程的同步等待


当我们在主线程中创建了10个子线程,然后我们期望10个子线程全部结束后,主线程在执行接下来的逻辑,这时,就该Thread.join登场了。

Thread.join示例
 public class JoinSample {

     public static void main(String[] args) throws InterruptedException
     {
         joinTest();
     }

     private static void joinTest() throws InterruptedException
     {
         Thread thread = new Thread()
         {
             public void run()
             {
                 try
                 {
                     for(int i = 0; i < 5; i++)
                     {
                         System.out.println("线程在运行。");
                         Thread.sleep(1000);
                     }
                 }
                 catch(InterruptedException ex)
                 {
                     ex.printStackTrace();
                 }
             }
         };
         thread.setDaemon(true);
         thread.start();
         Thread.sleep(1000);
         thread.join();
         System.out.println("主线程正常结束。");
     }
 }

我们可以试着将thread.join();注释或者删除,再次运行程序,就可以发现不同了。

线程间通信


我们知道,一个进程下面的所有线程是共享内存空间的,那么我们如何在不同的线程之间传递消息呢?在回顾 Java I/O时,我们谈到了PipedStream和PipedReader,这里,就是它们发挥作用的地方了。

下面的两个示例,功能完全一样,不同的是一个使用Stream,一个使用Reader/Writer。

PipeInputStream/PipedOutpueStream 示例
 public static void communicationTest() throws IOException, InterruptedException
 {
     final PipedOutputStream pos = new PipedOutputStream();
     final PipedInputStream pis = new PipedInputStream(pos);

     Thread thread1 = new Thread()
     {
         public void run()
         {
             BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
             try
             {
                 while(true)
                 {
                     String message = br.readLine();
                     pos.write(message.getBytes());
                     if (message.equals("end")) break;
                 }
                 br.close();
                 pos.close();
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
     };

     Thread thread2 = new Thread()
     {
         public void run()
         {
             byte[] buffer = new byte[1024];
             int bytesRead = 0;
             try
             {
                 while((bytesRead = pis.read(buffer, 0, buffer.length)) != -1)
                 {
                     System.out.println(new String(buffer));
                     if (new String(buffer).equals("end")) break;
                     buffer = null;
                     buffer = new byte[1024];
                 }
                 pis.close();
                 buffer = null;
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
     };

     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }
PipedReader/PipedWriter 示例
 private static void communicationTest2() throws InterruptedException, IOException
 {
     final PipedWriter pw = new PipedWriter();
     final PipedReader pr = new PipedReader(pw);
     final BufferedWriter bw = new BufferedWriter(pw);
     final BufferedReader br = new BufferedReader(pr);

     Thread thread1 = new Thread()
     {
         public void run()
         {

             BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
             try
             {
                 while(true)
                 {
                     String message = br.readLine();
                     bw.write(message);
                     bw.newLine();
                     bw.flush();
                     if (message.equals("end")) break;
                 }
                 br.close();
                 pw.close();
                 bw.close();
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
     };

     Thread thread2 = new Thread()
     {
         public void run()
         {

             String line = null;
             try
             {
                 while((line = br.readLine()) != null)
                 {
                     System.out.println(line);
                     if (line.equals("end")) break;
                 }
                 br.close();
                 pr.close();
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
     };

     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

 以上就是基于Java回顾之多线程详解的内容,更多相关内容请关注PHP中文网(www.php.cn)! 


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