9. 多線程 Part 2 --- 學習筆記


9.4 線程操作的相關方法

          在Java實現多線程的程序中,雖然Thread類實現了Runnable接口,但是操作線程的主要方法並不在Runnable接口中,而是在Thread類中。下面列出Thread類中的主要方法。

 

      9.4.1 取得和設置線程名稱

           在Thread類中可以通過getName()方法取得線程的名稱,還可以通過setName()方法設置線程的名稱。

           線程的名稱一般在啓動線程前就設置好了,但也允許爲已經運行的線程設置名稱。 允許兩個Thread對象有相同的名稱,但應該儘量避免這種情況的方法。

  • 如果沒有設置名稱,系統會爲其自動分配名稱。   在線程操作中,如果沒有爲一個線程指定一個名稱,則系統在使用時會爲線程分配一個名稱,名稱的格式是: Thread-Xx

例子: 取得和設置線程的名稱

class MyThread implements Runnable{
    public void run(){
        for(int i = 0; i < 3; i++){
            System.out.println(Thread.currentThread().getName() + "運行, i = " + i);
        }
    }
}
public class ThreadNameDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        new Thread(my).start();
        new Thread(my, "線程-A").start();
        new Thread(my, "線程-B").start();
        new Thread(my).start();
        new Thread(my).start();
    }
}

運行結果:

-------------------------------------------------
Thread-0運行, i = 0
Thread-0運行, i = 1
Thread-0運行, i = 2
線程-A運行, i = 0
線程-A運行, i = 1
線程-A運行, i = 2
線程-B運行, i = 0
線程-B運行, i = 1
線程-B運行, i = 2
Thread-1運行, i = 0
Thread-1運行, i = 1
Thread-1運行, i = 2
Thread-2運行, i = 0
Thread-2運行, i = 1
Thread-2運行, i = 2

-------------------------------------------------

    從運行結果可以發現,沒有設置線程名稱的3個線程對象的名稱都是很有規律的。分別是Thread-0、Thread-1、Thread-2。從之前講解到的static關鍵字可以知道,在Thread類中必然存在一個static類型的屬性,用於爲線程自動命名。

範例2

class MyThread implements Runnable{           //實現Runnable接口
    public void run(){                        //覆寫接口中的run()方法
        for (int i = 0; i < 3; i++){
            System.out.println(Thread.currentThread().getName() + "運行, i = " + i);
        }
    }
}
public class CurrentThreadDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();        //實例化MyThread對象
        new Thread(my,"線程-A").start();   //啓動一個新的線程
        my.run();                            //直接調用Mythread中的run()方法
    }
}

運行結果如下:

-------------------------------------------------
main運行, i = 0
main運行, i = 1
main運行, i = 2
線程-A運行, i = 0
線程-A運行, i = 1
線程-A運行, i = 2

-------------------------------------------------

   在以上程序中,主方法直接通過Runnable接口的子類對象調用其中的run()方法,另外一個是通過線程對象調用start()方法啓動的,從結果可以發現,主方法實際上也是一個線程。

    ********在java中所有的線程都是同時啓動的,哪個線程先搶到CPU資源,哪個就先運行********

  • java是多線程編程語言,所以java程序運行時也是以線程的方式運行的,那麼主方法也就是一個線程main。
  • 每當使用java命令執行一個類時,實際上都會啓動一個JVM,每一個JVM實際上就是在操作系統中啓動了一個進程(可以通過運行命令來驗證),java本身也具備了垃圾的收集機制。所以在java運行時至少會啓動兩個線程:  一個是main線程,另外一個就是垃圾收集線程GC!!!

      9.4.2 判斷線程是否啓動

            通過Thread類中的start()方法通知CPU這個線程已經準備好啓動,然後等待系統給它分配CPU資源,運行此線程。

            用戶可以通過Thread類中的isAlive()方法來測試線程是否已經啓動並且仍然在執行。

範例:判斷線程是否啓動

class MyThread implements Runnable{           //實現Runnable接口
    public void run(){                        //覆寫接口中的run()方法
        for (int i = 0; i < 2; i++){
            System.out.println(Thread.currentThread().getName() + "運行, i = " + i);
        }
    }
}
public class ThreadAliveDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();        //實例化MyThread對象
        Thread t = new Thread(my,"線程-A");
        System.out.println("線程開始執行之前  --> " + t.isAlive());
        t.start();
        System.out.println("線程開始執行之後  --> " + t.isAlive());
        for (int i = 0; i < 2; i++){
            System.out.println("main線程運行 --> " + i);
        }
        <span style="font-size:14px;color:#ff0000;">System.out.println("代碼執行之後  --> " + t.isAlive());</span>
    }
}

上面這段程序的運行結果,在代碼執行之後的線程可能仍在執行,也可能已經執行完畢。

-------------------------------------------------
線程開始執行之前  --> false
線程開始執行之後  --> true
main線程運行 --> 0
main線程運行 --> 1
<span style="font-size:14px;color:#ff0000;">代碼執行之後  --> true</span>
線程-A運行, i = 0
線程-A運行, i = 1

-------------------------------------------------

-------------------------------------------------
線程開始執行之前  --> false
線程開始執行之後  --> true
...
<span style="font-size:14px;color:#ff0000;">代碼執行之後  --> false</span>

-------------------------------------------------

       以上代碼紅色部分輸出結果是不確定的,有可能到最後線程已經不存活;但也有可能繼續存活。 這主要看哪個線程先執行完!!!

  • 因爲線程操作的不確定性,所以主線程有可能最先執行完,那麼此時其他線程不會受到任何影響,並不會隨着主線程的結束而結束!!!

      9.4.3 線程的強制運行

           在線程操作中,可以使用join()方法讓一個線程強制運行,線程強制運行期間,其他線程無法運行,必須等待此線程完成之後纔可以繼續執行。

  範例: 線程的強制運行

class MyThread implements Runnable{
    public void run(){
    	for (int i = 0; i < 2; i++){
        	System.out.println(Thread.currentThread().getName() + "運行, --> " + i);
    	}
    }
}
public class ThreadJoinDemo{
    public static void main(String args[]){
        MyThread mt = new MyThread();
        Thread t = new Thread(mt, "線程-A");
        t.start();
        for (int i = 0; i < 5; i++){
            if(i > 1){
                try{
                    <span style="font-size:14px;color:#ff0000;">t.join();</span>
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            System.out.println("Main線程運行  --> " + i);
        }
    }
}

運行結果:  主線程main必須等待線程-A完成之後才能繼續執行!!

-------------------------------------------------
Main線程運行  --> 0
Main線程運行  --> 1
線程-A運行, --> 0
線程-A運行, --> 1
Main線程運行  --> 2
Main線程運行  --> 3
Main線程運行  --> 4

-------------------------------------------------
當註釋掉上面程序中的紅色部分,就是不強制運行“線程-A”的時候的輸出結果是:
-------------------------------------------------
Main線程運行  --> 0
Main線程運行  --> 1
Main線程運行  --> 2
Main線程運行  --> 3
Main線程運行  --> 4
線程-A運行, --> 0
線程-A運行, --> 1

-------------------------------------------------


      9.4.4 線程的休眠

              可以直接使用Thread.sleep()方法來實現“讓一個線程在運行時暫時的休眠”

 範例:線程的休眠

class MyThread implements Runnable{
    public void run(){
        for(int i = 0; i < 3; i++){
            try{
                Thread.sleep(5000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "運行,i = " + i);
        }
    }
}
public class ThreadSleepDemo{
    public static void main(String args[]){
        MyThread mt = new MyThread();
        new Thread(mt, "線程-A").start();
    }
}

運行結果爲:

-------------------------------------------------
線程-A運行,i = 0

線程-A運行,i = 1

線程-A運行,i = 2

-------------------------------------------------

   上面程序的每次輸出都會間隔5000ms, 達到了延遲操作的效果。 

      9.4.5 中斷線程

              當一個線程運行時,另外一個線程可以直接通過interrupt()方法中斷其運行狀態。 線程一旦被中斷,會產生一個一個異常。。。

範例:線程的中斷

class MyThread implements Runnable{
    public void run(){
        System.out.println("1、 進入run方法了");
        try {
            Thread.sleep(10000);
            System.out.println("2、 已經完成休眠");
        }catch(InterruptedException e){
            System.out.println("3、 休眠被終止了");
            return;
        }
        System.out.println("4、 run方法正常結束");
    }
}
public class ThreadInterruptDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        Thread t = new Thread(my, "線程-A");
        t.start();
        try{
            Thread.sleep(2000);
        }catch(InterruptedException e){}
        t.interrupt();
    }
}

運行結果:

-------------------------------------------------
1、 進入run方法了

3、 休眠被終止了

-------------------------------------------------

    上面程序,一個線程啓動之後進入了休眠狀態,原來是要休眠10s之後再繼續執行,但是主方法在線程啓動之後的兩秒就中斷了該線程。休眠一旦中斷後將執行catch中的代碼。

      9.4.6 後臺線程

              在Java程序中,只要前臺有一個線程在運行,則整個java進程都不會消失,所以此時可以設置一個後臺線程,這樣即使java進程結束了,此後臺線程依然會繼續執行。 要想實現這樣的操作,直接使用setDaemon()方法即可。

範例: 後臺線程設置

import java.io.*;
class MyThread implements Runnable{
    public void run(){
        while(true){
            System.out.println(Thread.currentThread().getName() + "在運行。");  //輸出線程名稱
        }
    }
}
public class ThreadDaemonDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        Thread t = new Thread(my, "線程-A");
        t.setDaemon(true);           //此線程在後臺運行
        t.start();
        try{
            System.in.read();   //接收輸入,使程序在此停頓,一旦接收到用戶輸入,main線程終止,自動結束
        }catch(IOException e){
            
        }
    }
}

運行結果:

若主線程main沒有結束(用戶沒有輸入操作),那麼後臺線程(守護線程)會一直運行,不斷輸出;如果用戶輸入後,主線程main會結束,由於上面程序中除了主線程main一個前臺線程(用戶線程)之外,沒有其他的用戶線程。所以JVM判斷進程當中沒有用戶線程在運行,會結束掉所有守護線程的運行(包括垃圾收集線程GC),並且結束掉該進程!!!

   在線程類MyThread中,儘管run()方法中是死循環的方式,但是程序依然可以執行完,因爲方法中的死循環已經設置成後臺運行了。。。。

補充:關於用戶線程和守護線程

  • 所謂守護線程就是運行在程序後臺的線程,程序的主線程main(程序已開始啓動該線程)不會是守護線程。
  • java虛擬機JVM在運行一個進程時,可能會同時有多個線程在運行,只有當所有的非守護線程都結束的時候,虛擬機的進程纔會結束,不管在運行的線程是不是main()線程。
  • main主線程結束了(Non-Daemon thread),如果此時在運行的其他線程是daemon thread,JVM會使得這個線程停止,JVM也停下。如果此時正在運行的其他線程有非守護線程,那麼必須等到所有的非守護線程結束了,JVM纔會停下來。
  • 總值,必須等所有的非守護線程都運行結束了,只剩下守護線程的時候,JVM會停止所有的非守護線程,然後JVM也會自己停止。
  • 主線程main是非守護線程。
  • 後臺線程(守護線程)表示的是當前任務中不是必不可少的線程,例如在main線程中啓動了一個後臺線程,那麼當main線程執行完之後,不管後臺線程是否執行完畢都會被殺死。。可以參考垃圾收集機制來理解GC也是一個守護線程!!!

      9.4.7 線程的優先級

              在java的線程操作中,所有的線程在運行前都會保持在就緒狀態,那麼此時,哪個線程的優先級高,哪個線程的就有可能會先被執行,如下圖所示: 

                       

         在java的線程中使用setPriority()方法可以設置一個線程的優先級,在java的線程中一共有3種優先級,如下表所示:

         

範例:測試線程優先級

class MyThread implements Runnable{
    public void run(){
        for (int i = 0; i < 2; i++){
            try{
                Thread.sleep(20);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "運行,i = " + i);
        }
    }
}
public class ThreadPriorityDemo{
    public static void main(String args[]){
        Thread t1 = new Thread(new MyThread(), "線程-A");
        Thread t2 = new Thread(new MyThread(), "線程-B");
        Thread t3 = new Thread(new MyThread(), "線程-C");
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(Thread.NORM_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}

運行結果:

-------------------------------------------------
線程-A運行,i = 0
線程-B運行,i = 0
線程-C運行,i = 0
線程-A運行,i = 1
線程-B運行,i = 1
線程-C運行,i = 1

-------------------------------------------------


並不是線程的優先級越高就一定會先執行,哪個線程先執行將由CPU的調度決定。上面的輸出結果就說了此情況。

  • 主方法的優先級是NORM_PRIORITY。可以通過getPriority()方法來獲取線程的優先等級。


      9.4.8 線程的禮讓

              在線程操作中,可以使用yield()方法將一個線程的操作暫時讓他其他線程執行。

範例:線程的禮讓

class MyThread implements Runnable{
    public void run(){
        for (int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + "運行, i = " + i);
            if (i == 3){
                System.out.println("線程禮讓:");
                Thread.currentThread().yield();    //線程禮讓
            }
        }
    }
}
public class ThreadYieldDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        Thread t1 = new Thread(my, "線程-A");
        Thread t2 = new Thread(my, "線程-B");
        t1.start();
        t2.start();
    }
}

運行結果:

-------------------------------------------------
線程-A運行, i = 0
線程-A運行, i = 1
線程-A運行, i = 2
線程-A運行, i = 3
線程禮讓:
線程-B運行, i = 0
線程-B運行, i = 1
線程-B運行, i = 2
線程-B運行, i = 3
線程禮讓:
線程-A運行, i = 4
線程-B運行, i = 4

-------------------------------------------------

每當線程滿足條件(I == 3),就會將本線程暫停,而讓其他線程執行。


9.5 線程操作範例

    設計一個線程操作類,可以產生3個線程對象,並分別設置三個線程的休眠時間,具體如下所示:

  • 線程A,休眠10秒
  • 線程B,休眠20秒
  • 線程C,休眠30秒

      9.5.1 實現一    繼承Thread類

class MyThread extends Thread{
    private int time;
    public MyThread(String name, int time){
        super(name);
        this.time = time;
    }
    public void run(){
        try{
            Thread.sleep(this.time);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "線程, 休眠" + this.time + "毫秒");
    }
}
public class ExecDemo01{
    public static void main(String args[]){
        MyThread my1 = new MyThread("線程-A", 10000);
        MyThread my2 = new MyThread("線程-B", 10000);
        MyThread my3 = new MyThread("線程-C", 10000);
        my1.start();
        my2.start();
        my3.start();
    }
}

運行結果:

-------------------------------------------------
線程-A線程, 休眠10000毫秒
線程-B線程, 休眠10000毫秒
線程-C線程, 休眠10000毫秒

-------------------------------------------------

      9.5.2 實現二    實現Runnable接口

class MyThread implements Runnable{
    //private String name;
    private int time;
    /*public MyThread(String name, int time){
        this.name = name;
        this.time = time;
    }*/
    public MyThread(int time){
        this.time = time;
    }
    public void run(){
        try{
            Thread.sleep(this.time);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        //System.out.println(this.name + "線程,休眠" + this.time + "毫秒");    //此時根本就沒有調用線程的真正線程名。
        System.out.println(Thread.currentThread().getName() + "線程,休眠" + this.time +"毫秒");
    }
}
public class ExecDemo02{
    public static void main(String args[]){
        MyThread my1 = new MyThread(10000);
        MyThread my2 = new MyThread(20000);
        MyThread my3 = new MyThread(30000);
        Thread t1 = new Thread(my1, "線程-A");
        Thread t2 = new Thread(my2, "線程-B");
        Thread t3 = new Thread(my3, "線程-C");
        t1.start();
        t2.start();
        t3.start();
    }
}

運行結果:

-------------------------------------------------
線程-A線程,休眠10000毫秒

線程-B線程,休眠20000毫秒

線程-C線程,休眠30000毫秒

------------------------------------------------
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章