在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
-------------------------------------------------
-------------------------------------------------
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毫秒
------------------------------------------------